What is the difference between Strategy pattern and Visitor Pattern?
The strategy pattern is like a 1:many relationship. When there is one type of object and I want to apply multiple operations to it, I use the strategy pattern. For example, if I have a Video class that encapsulates a video clip, I might want to compress it in different ways. So I create a bunch of strategy classes:
MpegCompression
AviCompression
QuickTimeCompression
and so on.
I think of the visitor pattern as a many:many relationship. Let's say my application grows to to include not just video, but audio clips as well. If I stick with the strategy pattern, I have to duplicate my compression classes-- one for video and one for audio:
MpegVideoCompression
MpegAudioCompression
and so on...
If I switch to the visitor pattern, I do not have to duplicate the strategy classes. I achieve my goal by adding methods:
MpegCompressionVisitor::compressVideo(Video object)
MpegCompressionVisitor::compressAudio(Audio object)
[UPDATE: with Java] I used the visitor pattern in a Java app. It came out a little different than described above. Here is a Java version for this example.
// Visitor interface
interface Compressor {
// Visitor methods
void compress(Video object);
void compress(Audio object);
}
// Visitor implementation
class MpegCompressor implements Compressor {
public void compress(Video object) {
// ...
}
public void compress(Audio object) {
// ...
}
}
And now the interface and class to be visited:
interface Compressible {
void accept(Compressor compressor);
}
class Video implements Compressible {
// If the Compressor is an instance of MpegCompressionVisitor,
// the system prints "Mpeg video compression"
void accept(Compressor compressor) {
compressor.compress(this);
}
A Strategy pattern is used to expose various algorithms to a standardized interface. A typical example could be a sort utility that would let the user (programmer) choose between various sort algorithms each called via the same interface.
A Visitor pattern lives at a different level. It details a mechanism with which objects can accept a reference to another object (the visitor) which exposes a predetermined interface that the target object can call upon itself. Of course, different visitors would present the same interface but have different implementations.
Coming back to our example, a collection of sort algorithms could be implemented either via the Strategy pattern or via the Visitor pattern.
With the Strategy method, each algorithm presents the same interface and takes arrays of target objects as parameters for example. With the Visitor pattern, it would be the target array that takes the "visiting" algorithm as a parameter. In this case, the target would "accept()" the selected visitor and call its "visit()" method upon invocation of the target's sort method in our example.
Two sides of the same coin...
Does this make sense?
A Visitor is a strategy but with multiple methods and it allows Double dispatch. The Visitor also allows for safe binding between two concrete objects at runtime.
Note: This is an example written in Java. For example C# introduced the dynamic
keyword, therefor the example of double dispatch is not useful in C#.
Strategy pattern
Consider the following example and the output:
package DesignPatterns;
public class CarGarageStrategyDemo
{
public static interface RepairStrategy
{
public void repair(Car car);
}
public static interface Car
{
public String getName();
public void repair(RepairStrategy repairStrategy);
}
public static class PorscheRepairStrategy implements RepairStrategy
{
@Override
public void repair(Car car) {
System.out.println("Repairing " + car.getName() + " with the Porsche repair strategy");
}
}
public static class FerrariRepairStrategy implements RepairStrategy
{
@Override
public void repair(Car car) {
System.out.println("Repairing " + car.getName() + " with the Ferrari repair strategy");
}
}
public static class Porsche implements Car
{
public String getName()
{
return "Porsche";
}
@Override
public void repair(RepairStrategy repairStrategy) {
repairStrategy.repair(this);
}
}
public static void main(String[] args)
{
Car porsche = new Porsche();
porsche.repair(new PorscheRepairStrategy()); //Repairing Porsche with the porsche repair strategy
}
}
The Strategy
pattern is working fine if there is no direct relationship between the strategy and the subject. For example, we don't want the following to happen:
...
public static void main(String[] args)
{
Car porsche = new Porsche();
porsche.repair(new FerrariRepairStrategy()); //We cannot repair a Porsche as a Ferrari!
}
...
So in this case we can use the visitor pattern.
Visitor
The problem
Consider the code below:
public class CarGarageVisitorProblem
{
public static interface Car
{
public String getName();
}
public static class Porsche implements Car
{
public String getName()
{
return "Porsche";
}
}
public static class Ferrari implements Car
{
public String getName()
{
return "Ferrari";
}
}
public void repair(Car car)
{
System.out.println("Applying a very generic and abstract repair");
}
public void repair(Porsche car)
{
System.out.println("Applying a very specific Porsche repair");
}
public void repair(Ferrari car)
{
System.out.println("Applying a very specific Ferrari repair");
}
public static void main(String[] args)
{
CarGarageVisitorProblem garage = new CarGarageVisitorProblem();
Porsche porsche = new Porsche();
garage.repair(porsche); //Applying a very specific Porsche repair
}
}
The output is Applying a very specific Porsche repair
.
The problem is that this line is not abstract, but concrete:
Porsche porsche = new Porsche();
We want to write it as (or inject an instance of Car in the constructor, we want to apply the Dependency Inversion Principle
):
Car porsche = new Porsche();
But when we change this line, the output will be:
Applying a very generic and abstract repair
Not what we want!
The solution; using double dispatch (and the Visitor pattern)
package DesignPatterns;
public class CarGarageVisitorExample
{
public static interface Car
{
public String getName();
public void repair(RepairVisitorInterface repairVisitor);
}
public static class Porsche implements Car
{
public String getName()
{
return "Porsche";
}
public void repair(RepairVisitorInterface repairVisitor)
{
repairVisitor.repair(this);
}
}
public static class Ferrari implements Car
{
public String getName()
{
return "Ferrari";
}
public void repair(RepairVisitorInterface repairVisitor)
{
repairVisitor.repair(this);
}
}
public static interface RepairVisitorInterface
{
public void repair(Car car);
public void repair(Porsche car);
public void repair(Ferrari car);
}
public static class RepairVisitor implements RepairVisitorInterface
{
public void repair(Car car)
{
System.out.println("Applying a very generic and abstract repair");
}
public void repair(Porsche car)
{
System.out.println("Applying a very specific Porsche repair");
}
public void repair(Ferrari car)
{
System.out.println("Applying a very specific Ferrari repair");
}
}
public static void main(String[] args)
{
CarGarageVisitor garage = new CarGarageVisitor();
Car porsche = new Porsche();
porsche.repair(new RepairVisitor()); //Applying a very specific Porsche repair
}
}
Because of method overloading, there is a concrete binding between the visitor and the subject (Car). There is no way a Porsche can be repaired as a Ferrari, since it uses method overloading. Also we solved the previously explained problem (that we cannot use Dependency Inversion
), by implementing this method:
public void repair(RepairVisitorInterface repairVisitor)
{
repairVisitor.repair(this);
}
The this
reference will return the concrete type of the object, not the abstract (Car) type.