I have been thinking a little bit about the visitor pattern recently. One of my most recent applications was in combination with a class, which would be used in a message distribution scenario.
The class’ reposibility is – given a list of email addresses – to look up any existing users in my application, and allow the caller to perform an action depending on whether the email belongs to a registered user or not. Pretty simple if you feel like juggling dictionaries and stuff, but I made an even simpler solution.
My email lookup service has the following interface:
|
public interface IUserEmailLookupService { IEnumerable<Recipient> GetRecipients(IEnumerable<string> emails); } |
And then I have two classes:
EmailRecipient and
UserRecipient which are both specializations of the abstract superclass
Recipient – each containing either an email (a
string) or an
IUser depending on whether the service was able to look up a user.
Then I have another service, which functions as a message distributor. Given a list of email addresses, either an email or an internal message will be delivered to each recipient. This is a perfect application for the visitor pattern, because it allows us to avoid this kind of brittle code:
|
// violates the open-closed principle if (recipient is EmailRecipient) { emailService.Send(((EmailRecipient)recipient).Email, message); } else if (recipient is UserRecipient) { messageService.Send(((UserRecipient)recipient).User, message); } else { throw new UnexpectedSpecializationException("But why??"); } |
I have seen this kind of code in far too many places where inheritance is used. It is obviously very brittle, because what happens if someone adds another kind of
Recipient? Nothing at first, because the code will compile like everything is allright. Only after the code is run, and we actually get in a situation where another kind of
Recipient is encountered, do we get an error… and in the example above, I was courteous and threw an exception – but what if the original author had not done that? The program could have been silently failing for months before someone found out… yikes!
Another solution is to move the behavior into the specializations – e.g. into an implementation of an abstract
SendMessage method on
Recipient. But then the specializations would require a reference to either the
emailService or the
messageService, which would kind of turn the layering upside down, since the domain classes would need to know about some higher level services.
What we really want, is for a service to be able to behave differently depending on which specialization is encountered, and for that service to contain the logic to handle each specialization. And we want errors at compile-time if a specialization is not handled. Enter the visitor pattern…
One way to accomplish this is by using the visitor pattern as described by GoF. It’s pretty simple: Make a call to an abstract method which gets implemented by each specialization, and let that specialization make a suitable callback to identify itself. My visitor interface looks like this:
|
public interface IRecipientVisitor { void VisitUserRecipient(UserRecipient recipient); void VisitEmailRecipient(EmailRecipient recipient); } |
– and then each specialization must implement the abstract
Visit method from the
Recipient class:
|
public abstract void Visit(IRecipientVisitor visitor); |
and then invoke their correponding callback method passing themselves back.
Then my client can create an implementation of
IRecipientVisitor, supplying it with the necessary services, allowing the visitor to do different stuff depending on the specializations. But that requires me to implement a class like this every time I have a reason to do different stuff:
|
class MessageDispatcherVisitor : IRecipientVisitor { // implements a message dispatch for each type of visit... } |
Having done this a few times, implementing a new class each time which would be used only once, I started thinking about how to accomplish this with fewer lines of code. One thing that really bugged me, was that I needed to supply the visitor with all kinds of context data to allow the visitor to perform whichever action it would decide to carry out, depending on which
Visit(...) method would be called. Then I came up with the generic visitor:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
public class RecipientVisitor : IRecipientVisitor { readonly Action<UserRecipient> handleUserRecipient; readonly Action<EmailRecipient> handleEmailRecipient; public RecipientVisitor(Action<UserRecipient> handleUserRecipient, Action<EmailRecipient> handleEmailRecipient) { this.handleUserRecipient = handleUserRecipient; this.handleEmailRecipient = handleEmailRecipient; } public override void VisitUserRecipient(UserRecipient recipient) { handleUserRecipient(recipient); } public override void VisitEmailRecipient(EmailRecipient recipient) { handleEmailRecipient(recipient); } } |
Now my message dispatch can be implemented like this:
|
var recipients = lookupService.GetRecipients(emails); var visitor = new RecipientVisitor(r => messageService.Send(r.User, message), r => emailService.Send(r.Email, message)); recipients.ForEach(r => r.Visit(visitor)); |
Why is this great?
1. I get to implement multiple dispatch with all the safety of the visitor pattern. If I add a new specialization of
Recipient, e.g.
ErronousEmailRecipient, I add a new method to the
IRecipientVisitor interface. That causes the
RecipientVisitor to break, so I implement the new method in there as well, in turn causing the code to break everywhere the visitor is used (“break” in a good not introducing bugs-kind-of-way). That way I can be sure that I will know where to handle this new specialization.
2. I get access to the context I am already in. Instead of creating a new visitor and supplying it with sufficient context to carry out whichever actions it needs to perform, I can specify in a short and concise way what I want to do for each specialization, accessing any local variables inside of the scope my labdas are in. That’s just elegant and easy.
3. The code is where it is used. The distance from usage to implementation is zero. Of course, if the implementation is more complex, it may look more like so:
|
var results = new List<Result>(); var visitor = new RecipientVisitor( r => { var message = GetMessageFromContentService("SomeUserMessage", r.User); var result = SendMessageAndGetResult(message, r.User); results.Add("SendMessage", result); }, { (...) } ); |
– which is still pretty terse and to-the-point compared to creating an entirely new dedicated class for this.