Visitor Design Pattern in Go

1. Definition

The Visitor Design Pattern represents an operation to be performed on the elements of an object structure. It allows you to define a new operation without changing the classes of the elements on which it operates.

2. Problem Statement

Suppose you have a collection of distinct and unrelated objects and you need to execute operations on these objects. If these operations keep changing or are unknown beforehand, then adding them directly to the classes can lead to frequent changes to existing code, violating the open-closed principle.

3. Solution

Introduce a visitor class that encapsulates the operations to be performed on each element of the object structure. The objects that make up the structure will provide an 'accept' method that takes the visitor as an argument, allowing the visitor to execute the operation on the object.

4. Real-World Use Cases

1. A computer graphics system where different shapes (like circle, square) can be processed by different operations (like draw, resize).

2. An XML or JSON parser that visits each node for various reasons like validation, transformation, etc.

5. Implementation Steps

1. Declare a Visitor interface that has a visit method for each type of element in the object structure.

2. Each element in the object structure must implement an 'Element' interface that has an 'accept' method.

3. Concrete elements implement the 'accept' method that takes a visitor and calls the visit method on it.

4. Concrete visitors implement the visit methods to execute operations on the elements.

6. Implementation in Go

// Element interface
type Element interface {
	accept(Visitor)
}
// Concrete elements: Circle and Square
type Circle struct {}
type Square struct {}
func (c *Circle) accept(v Visitor) {
	v.visitCircle(c)
}
func (s *Square) accept(v Visitor) {
	v.visitSquare(s)
}
// Visitor interface
type Visitor interface {
	visitCircle(*Circle)
	visitSquare(*Square)
}
// Concrete visitor: DrawVisitor
type DrawVisitor struct {}
func (d *DrawVisitor) visitCircle(c *Circle) {
	fmt.Println("Drawing a circle")
}
func (d *DrawVisitor) visitSquare(s *Square) {
	fmt.Println("Drawing a square")
}
// Client code
func main() {
	elements := []Element{&Circle{}, &Square{}}
	drawVisitor := &DrawVisitor{}
	for _, element := range elements {
		element.accept(drawVisitor)
	}
}

Output:

Drawing a circle
Drawing a square

Explanation:

1. The Element interface ensures that every concrete element (like Circle, Square) has an 'accept' method.

2. The 'accept' method in each concrete element receives a visitor and delegates the call to the visitor's visit method.

3. The Visitor interface ensures each visitor has visit methods for every concrete element.

4. The DrawVisitor implements the visit methods to provide specific drawing behavior for each shape.

7. When to use?

Use the Visitor pattern when:

1. An object structure contains many classes with differing interfaces, and you want to perform operations on these objects that depend on their concrete classes.

2. Many distinct and unrelated operations need to be performed on objects in an object structure, and you want to avoid making these operations part of the objects' classes.

3. The object structure rarely changes, but you often want to define new operations over the structure.


Comments