logo

Achieve Ultimate Excellence

Visitor Design Pattern - Add new Behaviors to existing classes without modifying them

Introduction:

Design patterns are proven solutions to common software design problems. One such pattern is the Visitor design pattern, which allows you to add new operations or behaviors to existing classes without modifying them. This pattern promotes separation of concerns and follows the open-closed principle, enabling easy extension of functionality.

What is the Visitor Design Pattern?

The Visitor design pattern is a behavioral design pattern that lets you define new operations on a set of related classes without changing the classes themselves. It achieves this by decoupling the data structure from the operations performed on it.

Key Components:

The Visitor pattern involves the following key components:

  • Visitor: An abstract or interface class that declares a set of visit methods, one for each element type in the object structure.

  • ConcreteVisitor: Concrete implementations of the Visitor interface that provide specific behaviors for each element type.

  • Element: An abstract or interface class representing the elements in the object structure. It declares an accept method that accepts a Visitor.

  • ConcreteElement: Concrete implementations of the Element interface that provide specific operations and accept the visitor.

  • ObjectStructure: A class that holds a collection of elements and provides methods to access and manipulate them.

Example: Implementing the Visitor Design Pattern in Java

Let's consider a simple scenario where we have a set of geometric shapes: Circle, Square, and Triangle. We want to calculate the area and perimeter of each shape without modifying their classes.

Step 1: Define the Element interface and ConcreteElement classes:

// Element interface
interface Shape {
    void accept(ShapeVisitor visitor);
}

// ConcreteElement: Circle
class Circle implements Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    public double getRadius() {
        return radius;
    }

    @Override
    public void accept(ShapeVisitor visitor) {
        visitor.visit(this);
    }
}

// ConcreteElement: Square
class Square implements Shape {
    private double side;

    public Square(double side) {
        this.side = side;
    }

    public double getSide() {
        return side;
    }

    @Override
    public void accept(ShapeVisitor visitor) {
        visitor.visit(this);
    }
}

// ConcreteElement: Triangle
class Triangle implements Shape {
    private double a, b, c;

    public Triangle(double a, double b, double c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }

    public double getA() {
        return a;
    }

    public double getB() {
        return b;
    }

    public double getC() {
        return c;
    }

    @Override
    public void accept(ShapeVisitor visitor) {
        visitor.visit(this);
    }
}

Step 2: Create the Visitor interface and ConcreteVisitor classes:

// Visitor interface
interface ShapeVisitor {
    void visit(Circle circle);
    void visit(Square square);
    void visit(Triangle triangle);
}

// ConcreteVisitor: AreaCalculator
class AreaCalculator implements ShapeVisitor {
    private double areaSum = 0;

    @Override
    public void visit(Circle circle) {
        double area = Math.PI * Math.pow(circle.getRadius(), 2);
        areaSum += area;
    }

    @Override
    public void visit(Square square) {
        double area = Math.pow(square.getSide(), 2);
        areaSum += area;
    }

    @Override
    public void visit(Triangle triangle) {
        // Using Heron's formula to calculate area of a triangle
        double s = (triangle.getA() + triangle.getB() + triangle.getC()) / 2;
        double area = Math.sqrt(s * (s - triangle.getA()) * (s - triangle.getB()) * (s - triangle.getC()));
        areaSum += area;
    }

    public double getTotalArea() {
        return areaSum;
    }
}

// ConcreteVisitor: PerimeterCalculator
class PerimeterCalculator implements ShapeVisitor {
    private double perimeterSum = 0;

    @Override
    public void visit(Circle circle) {
        double perimeter = 2 * Math.PI * circle.getRadius();
        perimeterSum += perimeter;
    }

    @Override
    public void visit(Square square) {
        double perimeter = 4 * square.getSide();
        perimeterSum += perimeter;
    }

    @Override
    public void visit(Triangle triangle) {
        double perimeter = triangle.getA() + triangle.getB() + triangle.getC();
        perimeterSum += perimeter;
    }

    public double getTotalPerimeter() {
        return perimeterSum;
    }
}

Step 3: Create the ObjectStructure class to hold the collection of shapes:

import java.util.ArrayList;
import java.util.List;

// ObjectStructure
class ShapeCollection {
    private List<Shape> shapes = new ArrayList<>();

    public void addShape(Shape shape) {
        shapes.add(shape);
    }

    public void removeShape(Shape shape) {
        shapes.remove(shape);
    }

    public void accept(ShapeVisitor visitor) {
        for (Shape shape : shapes) {
            shape.accept(visitor);
        }
    }
}

Step 4: Putting it all together and testing the pattern:

public class Main {
    public static void main(String[] args) {
        ShapeCollection shapeCollection = new ShapeCollection();
        shapeCollection.addShape(new Circle(5));
        shapeCollection.addShape(new Square(4));
        shapeCollection.addShape(new Triangle(3, 4, 5));

        AreaCalculator areaCalculator = new AreaCalculator();
        shapeCollection.accept(areaCalculator);
        System.out.println("Total area: " + areaCalculator.getTotalArea());

        PerimeterCalculator perimeterCalculator = new PerimeterCalculator();
        shapeCollection.accept(perimeterCalculator);
        System.out.println("Total perimeter: " + perimeterCalculator.getTotalPerimeter());
    }
}

Conclusion:

The Visitor design pattern allows you to add new operations to a set of related classes without modifying their code. By following this pattern, you can achieve a flexible and maintainable design that separates concerns and promotes code extensibility. In our example, we demonstrated how to calculate the area and perimeter of geometric shapes using the Visitor pattern

avatar
Article By,
Create by
Browse Articles by Related Categories
Browse Articles by Related Tags
Share Article on:

Related posts

Related posts