Visitors: Chapter 2
Last time we talked about a case where a class hierarchy was being hopelessly overwhelmed with unrelated methods. Cruft was building up, and we wanted a way to create new logic for the class hierarchy without polluting the hierarchy itself.
Visitors solve these problems quite nicely. You can separate logic from the expression hierarchy, and keep different operations (printing, evaluating, etc) organized and in one place. It doesn’t require much boilerplate, either. Let’s see how it works:
To implement a Visitor pattern for your hierarchy, you first create a Visitor interface:
Then, you add an accept method to your hierarchy:
This is actually the only method your entire hierarchy will ever need, and you can then attach any behavior you want. Also, users of your library can also easily attach behavior to your expression hierarchy, which is an added bonus.
Each expression type implements this method and invokes the visit method. For example:
Then in the Visitor itself, you provide several strongly typed visit methods:
And that’s the basics of the pattern. Now let’s see how you add operations. We’ll start by adding print behavior:
This PrintVisitor can be used like so:
Next let’s move the evaluation code to a visitor:
Pretty neat, eh? We’ve gotten rid of two methods from our complicated class hierarchy and encapsulated all of the logic of those operations in one place. But there’s no such thing as a free lunch. Let’s talk about the tradeoffs.
First, you may notice that the logic is separated from the Expr class. That’s the whole point of the pattern, but it is a tradeoff. Usually, you want logic to be as close to the class as possible, and you want functions to have descriptive names and perform the task they’re named for. All of this is thrown out the window with Visitors. Instead, you should use good names for your Visitors, and just be happy that your expression classes don’t have to have thirty methods on them.
There’s also the obvious drawback of boilerplate. Dreaded boilerplate. Every time you want to use a Visitor, you have to go through this song and dance of creating the Visitor, calling accept, and getting some result from the Visitor. A lot of these problems can be ameliorated with a few tricks, but even if you hide this stuff behind some library, it still exists, and it is much more complicated than just calling some method and getting a result.
To everything, there is a season, and a time to every purpose. Should you use Visitors all the time? Hell no. Should you carefully analyze your problem, consider different designs, contemplate the pros and cons of each design, and maybe use Visitors if you decide that they work very well to solve your problem? Yes.