Monday, December 15, 2008

Design Patterns: Visitor vs. Strategy (with C# .NET Sample)

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.

image

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.

image

image

image The good thing is that it's all right in front of you, if you want to browse through the operations you don't have to look around, you go directly to the basic object model.

image 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.

Strategy

With the Strategy approach there's a separate strategy object for each one of the operations. image

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. 

 image

imageWith this approach the basic object model is not required to change with every new operation, and operation can be gracefully replaced on the fly.

image 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.

Visitor

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.

image

When the element is requested to do a wash - it calls the appropriate method on the visitor. 

image

imageWith 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.

image 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.

Summery

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.