Contents
Visitor
From GoF: Represent an operation to be performed on the elements of an object structure. Visitor lets you define the new operation without changing the classes of the elements on which it operates.
GoF Class Diagram
(NB: the code snippets are from the overridden AcceptVisitor methods)
Discussion
The classes in the composite to be Visited must be set up to receive a Visitor. Once that is done, any number of Visitor types can be created without changing the classes to be visited. In trhe diagram, the abstract Element class has an AcceptVisitor method which takes an abstract Visitor instance. ConcreteA and ConcteteB, the actual implemented classes in the structure, inherit this. They know to call VisitConcreteA or VisitConcreteB on the Visitor passing themselves to the call. The concrete visitor then does what it's supposed to do with that class and returns. The composite then calls AcceptVisitor on the next member of the composite as shown in the sequence diagram below. The class diagram assumes that the object structure to be visited is a Composite. This does not have to be the case. Any object structure which can navigate through itself is a candidate for accepting a Visitor.
Thus by implementing different types of Concrete Visitor different operations can be performed on the object structure.
One example of this is if a compound object (say an Order, containing Lines which in turn can contain lower-level breakdowns) is to be serialised in several ways: to XML, to a flat-file containing variable-length rows with fixed-length fields, etc.
A different type of visitor can be created for each type of serialisation, each of which knows how to serialise the classes in the Order to the intended format.
One drawback of the pattern is that it introduces a cyclic dependency: the visitor depends on the classes to be visited (i.e. cannot be compiled without their definition) and those same concrete classes depend on the Visitor. This means (in .NET) that the Visitor and the visited classes must be in the same assembly. This is not always desirable. Robert C Martin has a version of the pattern he calls Acyclic Visitor - note that this was created for C++ implementation and uses multiple inheritance. Similar effects can be gained by extracting interfaces to a third assembly which the other two (containing the elements to be visited in one and the Visitors in the other) depend. The elements and the Visitors then implement the appropriate interface. (This is a general way of getting round a cyclic dependency and is a technique worth remembering.)
Control of the sequence of calls to AcceptVisitor in the elements of the composite must be thought out. There are two approaches: the 'top' of the composite structure can iterate through all elements of the structure and call AcceptVisitor on each in turn, or each element in the structure calls AcceptVisitor on all of its children in their own AcceptVisitor method. While the latter looks cleaner code, it does mean that more intelligence is spread throughout each of the objects in the structure; this is not necessarily a bad thing. An example of the latter would be:
public abstract class SerialisingVisitor {
public abstract void VisitOrder(Order anOrder);
public abstract void VisitOrderLine(OrderLine aLine);
public abstract void VisitOrderLineKitElement
(OrderLineKitElement kitElement);
}
public class Order {
private ArrayList orderLines;
public void AcceptVisitor(SerialisingVisitor visitor) {
visitor.VisitOrder(this);
foreach (OrderLine line in orderLines) {
line.AcceptVisitor(visitor);
}
}
}
public class OrderLine {
private ArrayList kitElements;
public void AcceptVisitor(SerialisingVisitor visitor) {
visitor.VisitOrderLine(this);
if (kitElements != null) {
foreach (OrderLineKitElement kit in kitElements) {
kit.AcceptVisitor(visitor);
}
}
}
}
public class OrderLineKitElement {
public void AcceptVisitor(SerialisingVisitor visitor) {
visitor.VisitOrderLineKitElement(this);
}
}
Question: Was it a good idea to call the abstract visitor SerialisingVisitor? If I want to create a Visitor to do something else entirely (say apply discounts) to an Order I now have to create matching overloaded methods which take a DiscountVisitor. Would it not have been better to call the visitor OrderVisitor and derive SerialisingVisitors and DiscountVisitors from that? With overloaded methods you can explicitly see which type of operation the visitor is carrying out (in a debugger: static inspection won't tell you); on the other hand you have a lot of duplicated code. With an OrderVisitor there is no duplicated code, but a decent debugger will show you the type of the visitor. Majority decision to the OrderVisitor, IMHO.
Note that the amount of code is slightly greater (class declarations etc) when using the Visitor pattern versus putting the 'action' code in the classes to be acted on. However the code is much better organised: the Order classes do not contain anything to do with serialisation which is good as that is not their primary responsibility; knowledge of how to serialise an order in a given way is encapsulated in the appropriate Visitor implementation and not spread out through the classes involved in the order. Adding another serialisation method merely involves creating a new Visitor implementation, not changing all the classes involved in the Order. This results in much cleaner, more maintainable, code.
Notes from around the Web
From Vince Huston's pages:
- Visitor's primary purpose is to abstract functionality that can be applied to an aggregate hierarchy of "element" objects. The approach encourages designing lightweight Element classes - because processing functionality is removed from their list of responsibilities. New functionality can easily be added to the original inheritance hierarchy by creating a new Visitor subclass.
- Airfix models can be viewed as an object structure. Navigable models can accept a Visitor such as G14Paint. If G14 turns out to be inappropriate, then a G17Paint visitor can easily be developed and substituted without changing the model.
- Visitor implements "double dispatch". OO messages routinely manifest "single dispatch" - the operation that is executed depends on: the name of the request, and the type of the receiver. In "double dispatch", the operation executed depends on: the name of the request, and the type of TWO receivers (the type of the Visitor and the type of the element it visits).
- An acknowledged objection to the Visitor pattern is that it represents a regression to functional decomposition - separate the algorithms from the data structures. While this is a legitimate interpretation, perhaps a better perspective/rationale is the goal of promoting non-traditional behaviour to full object status.
- The Visitor pattern is like a more powerful Command pattern because the visitor may initiate whatever is appropriate for the kind of object it encounters.
- Visitor is not good for the situation where "visited" classes are not stable. Every time a new Composite hierarchy derived class is added, every Visitor derived class must be amended. {Note that there is an implementation at http://www.surguy.net/articles/visitor-with-reflection.xml (in Java) using reflection which promises to get around this objection. The converse � unstable Visitors - is not true: adding or changing a Visitor has no effect on the composite classes � save possible recompilation. svs}