Loading...

Visitor Pattern Intro

Home / Series 1 / Visitor Pattern Intro

Object structures tend to mimic life. Which is to say, that they’re a complete mess. But thanks to the Visitor Pattern, working with messy object structures doesn’t have to be horrible.

The Visitor Pattern works on object structures. Not just any structures, but wild and discordant ones. The kinds with lots of objects, lots of different kinds of objects. And in that mess, the Visitor Pattern serves as a blueprint for… processing everything. For changing or counting or reporting on different items — and doing so with any criteria imaginable.

The Visitor Pattern is kinda unique among design patterns. Most of us wouldn’t conjure it up naturally (at least I wouldn’t). It’s also got some hefty limitations. But it’s pretty darn useful when applicable.

To see how, let’s ponder us a calendar.

Code

As life grows more complex, so too, it seems, do our calendars. We’ve got events, holidays, multiday events, TODOs, and keep adding more.

How on Earth do we process this craziness?

So we’ve got a complex-ish object structure on our hands. Different types and one with multiple child elements. So how do we go about processing the data here? How do we do something easy like, find events that take place on even days of the month?

Just eye-balling it, we had a “Call with Bob” on the 22nd. There was a TODO blog post on the 14th. More subtle is the vacation time from the 27th through the 29th (which includes the 28th). So let’s find those three events… with the visitor pattern.

The overall approach is this. We’ll need an evensFinder. The calendar will accept a “visit” from evensFinder. After which, evensFinder will have the three even day events in its events property.

To make that work, we’ll start by teaching Calendar and calendar items how to accept visits. Then we’ll define objects like EvenDayFinder that are doing the visiting.

OK, the Calendar needs an accept() method. It’ll take a single argument–a CalendarVisitor. We’ll make EvenDayFinder implement that CalendarVisitor interface. The accept() method’s job is to tell each item on the calendar to, in turn, accept a visit from the same visitor.

Every “visitable” element on the calendar needs to follow the same basic pattern. Each will accept() a CalendarVisitorobject. And, each visitable element will turn around and tell the visitor (like our even-days-finder) to process their specific type of object.

When a basic calendar event, like our call with Bob, accepts a visit, it introduces itself to the visitor. The event tells the visitor to… visit the event. It’s important here to tell the visitor that it’s processing an event and not some other kind of object. We’ll see why in a second.

We follow the same pattern for all visitable elements in the calendar. Each needs to accept a visit from a CalendarVisitorand process that particular element type.

The ToDoList object is slightly different. It too introduces itself to the visitor (via visitToDoList()). Additionally, when it accepts a visit, it also has to tell each of its child elements — each individual ToDoItem — to also accept a visit. This is the basic approach to take with rich, deep structures.

Once we’re done with the visitable elements in the calendar, it’s time to build the thing that visits them. At this point, we know what the visitor interface needs to look like. It has to know how to visit an Event, a MultiDayEvent, a ToDoList, a ToDoItem, and a Holiday.

Soooo… EvenDayFinder needs to implement this CalendarVisitor interface. It’ll accumulate even day events in the events property. Now… we define the first visit method: visitEvent(). When EvenDayFinder visits a basic calendar event, the event introduces itself back to EvenDayFinder by calling this visitEvent() method, with the event as the argument. Handling the visit is simple enough: if this basic calendar event is on an even day, we add it to our events list.

With that, we’ve actually got a functional, if incomplete, visitor pattern implementation. The visitable elements in the calendar all know how to accept visits — each calls the corresponding processing method in the visitor. And our EvenDayFinderimplements each of those, even when they’re all empty save for the basic visitEvent() method.

But visitEvent() is defined and working. It finds the events that fall on an even day — right now, that’s the call with Bob.

When the even-days visitor visits a multi-day event, it adds the element when it starts on an even day. Or if the duration of the multi-day event is longer than a single day. With that, our well-deserved multi-day vacation is added to the list.

Finally we add TODO items to the list if their completion date is on an even day… and if they have a completion date.

And now our visitor can work through the entire structure and correctly find each event on an even day. Nice!

Visiting calendars to find "even" day events

Conclusion

There’s power here. But there are definite caveats. We’ll talk both as we further explore the visitor pattern.

The real payoff comes when we add another concrete visitor. Nothing else need change — it can hook right into visitable elements and… voila!

Most of the drawbacks boil down to this: the object structure should be stable. Each time a new object type gets added, the visitor — and all concrete visitors — need a new visit method. That gets old quick.

But under the right circumstances… wow is this a thunky little pattern.

Comments(0)

    Leave a Comment