The Visitor Pattern lets you build the structure for the double-dispatch side of the problem, helping you deal with the complexity that is due to your inheritance structure of your model. However, the classical form of the pattern does nothing to deal with the complexity that is due to the compositional structure of your model, especially when the same class is used multiple times in different capacities.
In your case, a solution must work out both of these complexities - on one hand, you have the Owner
vs. the Owned
; on the other side, you have the Liked
, Disliked
, and whatever else that you plan to add.
The task of dealing with the composition side is traditionally given to implementations of the visitor, not to the interface. However, the compiler would not be able to help you find violators that fail to process the newly relationships. However, you can combine the visitor pattern with the Template Method Pattern to create a hybrid solution that deals with both problems.
Here is a skeleton of what you can do:
// This is a run-of-the-mill visitor
interface IVisitor {
void VisitOwner(Owner owner);
void VisitOwned(Owned owned);
}
// This is a base visitor class; it is abstract
abstract class DefaultVisitor : IVisitor {
public void VisitOwner(Owner owner) {
BeginOwner(owner);
BeginLiked();
foreach (var owned in owner.Liked) {
owned.Accept(this);
}
EndLiked();
BeginDisliked();
foreach (var owned in owner.Disliked) {
owned.Accept(this);
}
EndDisliked();
EndOwner(owner);
}
public void VisitOwned(Owned owned) {
BeginOwned(owned);
EndOwned(owned);
}
public abstract void BeginOwner(Owner owner);
public abstract void EndOwner(Owner owner);
public abstract void BeginOwned(Owned owned);
public abstract void EndOwned(Owned owned);
public abstract void BeginLiked();
public abstract void EndLiked();
public abstract void BeginDisliked();
public abstract void EndDisliked();
}
The advantage of structuring your code like that is that the compiler is now on the hook for checking implementations of DefaultVisitor
for presence of all abstract methods; the disadvantage is that these implementations will have to provide eight implementations instead of two.