Visitor

Many times, we want to do different things with elements (nodes) of an aggregate (array, tree, list, and so on). The naive approach is, of course, to add methods on the nodes for each functionality. If there are different type of nodes then the concrete nodes all have to implement each processing function. As you can see, this approach is not ideal and defeats the segregation principles and introduces a lot of coupling between the nodes and the different types of processing.

The objective of the visitor design pattern is to encapsulate and isolate processing that needs be done to each element (node) of an aggregate. This avoids the method pollution that we described in the earlier paragraph. 

The key elements of the pattern are these:

  • Visitor: This class defines the interfaces for various processing and has a single method, visit(), which takes a node object as an argument. Concrete visitor classes describe the actual node processing in their implementation visit() method.
  • Node: This is the element of the aggregate on which we want to do the visiting. It has an accept() method that takes visitor as argument and starts the processing by calling the visitor's visit() method. Each concrete node can add more functionality to this as needed.

In most design patterns, polymorphism is implemented through single dispatch; that is to say, the operation being executed depends on the type of the called object. In visitor, we see the double dispatch variation: the operation to be executed finally depends both on the called object (the concrete node object), as well as the caller object (the concrete visitor):

New processing can easily be added by just subclassing to the original inheritance hierarchy by creating a new visitor subclass. Here is some sample code in Go:

// the Node interface
type Node interface {
Accept(Visitor)
}

type ConcreteNodeX struct{}
func (n ConcreteNodeX) Accept(visitor Visitor) {
visitor.Visit(n)
}

type ConcreteNodeY struct{}
func (n ConcreteNodeY) Accept(visitor Visitor) {
// do something NodeY-specific before visiting
fmt.Println("ConcreteNodeY being visited !")
visitor.Visit(n)
}


// the Vistor interface
type Visitor interface {
Visit(Node)
}

// and an implementation
type ConcreteVisitor struct{}
func (v ConcreteVisitor) Visit(node Node) {
fmt.Println("doing something concrete")
// since there is no function overloading..
// this is one way of checking the concrete node type
switch node.(type) {
case ConcreteNodeX:
fmt.Println("on Node ")
case ConcreteNodeY:
fmt.Println("on Node Y")
}
}

The client code will look as follows:

func main() {
// a simple aggregate
aggregate:= []Node {ConcreteNodeX{}, ConcreteNodeY{},}

// a vistor
visitor:= new(ConcreteVisitor)

// iterate and visit
for _, node:= range(aggregate){
node.Accept(visitor)
}

}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset