Both visitor and strategy design patterns offer a way of separating an algorithm from an object, in this post we'll review the implementation of the patterns in a simple car wash application and try to figure out in which cases each pattern should be applied.
Basic Object Model
The Car object is composed out of wheel, engine and body, each implements ICarElement.
As for now, we know that the application should support 2 kind of operations:
1. washing the car by steam.
2. washing by water.
however, the design should support more operations that will be added in the future.
Straight forward Implementation
With the most simple and straight forward approach the operations are implemented in the car elements body.
The problem with the straight forward approach is that the basic object model has to be changed with each new operation that will be required in the future, as for each new operation all the elements of the car (wheel, engine and body) have to be added with an extra function. Also, the tight coupling between the basic object model and the operation makes it harder to replace operation implementation of the fly.
The element is assigned with the proper strategy, and when it is requested to do a wash - it delegates the request to the strategy that performs the wash.
The problem is that there are too many strategy objects, in order to replace a washing technic from 'by water' to 'by stream' - three strategies have to be replaced. Also, with each new operation three strategies (one for each element) have to be added to the application.
With the Visitor approach there's one visitor object for each kind of operation ('wash by water', 'wash by steam'); the visitor object contains operation implementation for all the elements of the car (wheel, engine and body). For example, the 'stream wash' operation kind has SteamWashVisitor that contains steam washing implementation for all the elements of the car.
When the element is requested to do a wash - it calls the appropriate method on the visitor.
With this approach the basic object model is not required to changed with every new operation, and operation implementations for all the elements are bundled in one Visitor that can be replaced as a whole. For example, in order to replace a washing technic from 'by water' to 'by stream' - all we have to do is to replace the Visitor object from WaterWashVisitor to SteamWashVisitor.
When ever the basic object model changes all the Visitors have to be changed as well, for example, if a new 'Door' object is added to the car - all the visitors have to be added with an extra 'VisitDoor' method. This is why the Visitor pattern is applicable only if the basic object model doesn't tend to change.
In most cases the operations are implemented in the body of the object on which they operate.
Often, operations are implemented in an external package or have to be replaced of the fly according to application rules, for this strategies can be used to allow external operations injection and graceful operations replacement.
In other cases (not that often) the same kind of operation has to be executed on several elements (that usually share interface) in the object model, and there's a need to easily replace the entire set of operations on the fly. Only in condition that the object model is steady and doesn't tend to change - visitors can be used to group set of operations in one object that can be easily replaced.