Visitor Design Pattern in Swift

1. Definition

The Visitor pattern is a behavioral design pattern that allows adding further operations to objects without having to modify them. It involves a visitor class and a set of element classes, where the visitor class can perform an operation on each element class without modifying the original classes.

2. Problem Statement

Imagine you have a structure representing a geometric shape hierarchy (like circles, rectangles, and triangles). Now, if you want to introduce the capability to compute the area, perimeter, or other operations on these shapes, altering the shape classes directly can violate the Open-Closed Principle.

3. Solution

The Visitor pattern suggests moving the new operations into a separate class called Visitor. Elements accept a visitor and then let it "visit" them. The visitor then performs the required operation on the element, allowing you to add new behaviors without altering existing class definitions.

4. Real-World Use Cases

1. Rendering different file formats from a document structure.

2. Performing operations on various elements of a composite structure, like XML or JSON nodes.

5. Implementation Steps

1. Declare a visitor interface with a visit method for each type of concrete element.

2. Declare an element interface, including an accept method that takes a visitor as an argument.

3. Implement concrete element classes, each having the accept method.

4. Create concrete visitor classes for the operations you want to implement.

5. Use the visitor on the elements when you want to perform the operation.

6. Implementation in Swift Programming

// Element protocol
protocol Shape {
    func accept(visitor: ShapeVisitor)
}
// Concrete elements
class Circle: Shape {
    func accept(visitor: ShapeVisitor) {
        visitor.visit(circle: self)
    }
}
class Rectangle: Shape {
    func accept(visitor: ShapeVisitor) {
        visitor.visit(rectangle: self)
    }
}
// Visitor protocol
protocol ShapeVisitor {
    func visit(circle: Circle)
    func visit(rectangle: Rectangle)
}
// Concrete Visitor
class AreaVisitor: ShapeVisitor {
    func visit(circle: Circle) {
        print("Computing area for Circle...")
    }
    func visit(rectangle: Rectangle) {
        print("Computing area for Rectangle...")
    }
}
// Usage
let shapes: [Shape] = [Circle(), Rectangle()]
let areaVisitor = AreaVisitor()
for shape in shapes {
    shape.accept(visitor: areaVisitor)
}

Output:

Computing area for Circle...
Computing area for Rectangle...

Explanation:

1. The Shape protocol represents the element interface and includes the accept method that takes a visitor.

2. Concrete element classes (Circle and Rectangle) implement the accept method and allow the visitor to "visit" them.

3. The ShapeVisitor protocol represents the visitor interface, having a visit method for each concrete element.

4. The AreaVisitor class is a concrete visitor that implements the visit method to compute the area of the shapes.

5. The client code (in the Usage section) demonstrates applying the AreaVisitor to the collection of shapes.

7. When to use?

Use the Visitor pattern when:

1. You need to add operations to classes without modifying them.

2. The operations applied are distinct from the classes themselves.

3. You want to keep related operations together by defining them in one class.


Comments