I guess many people know about the Visitor design pattern, described in the Gang of Four’s Design Patterns: Elements of Reusable Object-Oriented Software book. The pattern itself is not very complex (as many design patterns go):

I’ve known Visitor since ages, but I’ve never needed it…​ yet. Java handles polymorphism natively: the method call is based upon the runtime type of the calling object, not on its compile type.

interface Animal { void eat (); } public class Dog implements Animal { public void eat () { System . out . println ( "Gnaws bones" ); } } Animal a = new Dog (); a . eats (); // Prints "Gnaws bones"

However, this doesn’t work so well (i.e. at all) for parameter types:

public class Feeder { public void feed ( Dog d ) { d . eat (); } public void feed ( Cat c ) { c . eat (); } } Feeder feeder = new Feeder (); Object o = new Dog (); feeder . feed ( o ); // Cannot compile!

This issue is called double dispatch as it requires calling a method based on both instance and parameter types, which Java doesn’t handle natively. In order to make it compile, the following code is required:

if ( o instanceof Dog ) { feeder . feed (( Dog ) o ); } else if ( o instanceof Cat ) { feeder . feed (( Cat ) o ); } else { throw new RuntimeException ( "Invalid type" ); }

This gets even more complex with more overloaded methods available - and exponentially so with more parameters. In maintenance phase, adding more overloaded methods requires reading the whole if stuff and updating it. Multiple parameters are implemented through embedded if , which is even worse regarding maintainability. The Visitor pattern is an elegant way to achieve the same, with no if , at the expense of a single method on the Animal class.

public interface Animal { void eat (); void accept ( Visitor v ); } public class Cat { public void eat () { ... } public void accept ( Visitor v ) { v . visit ( this ); } } public class Dog { public void eat () { ... } public void accept ( Visitor v ) { v . visit ( this ); } } public class FeederVisitor { public void visit ( Cat c ) { new Feeder (). feed ( c ); } public void visit ( Dog d ) { new Feeder (). feed ( d ); } }

Benefits:

No evaluation logic anywhere

Only adherence between Animal and FeederVisitor is limited to the visit() method

As a corollary, when adding new Animal subtypes, the Feeder type is left untouched

When adding new Animal subtypes, the FeederVisitor type may implement an additional method to handle it

Other cross-cutting logic may follow the same pattern, e.g. a train feature to teach animals new tricks