Visitor, the last pattern found in the relatively young, but already classic book Design Patterns: Elements of Reusable Object-Oriented Software, by Gamma et. al., is often a stumbling block for both students and professionals who are new to the idea of patterns. Some patterns in the book, such as Strategy and Adapter are so intuitive that a programmer who merely hears the name can guess what the pattern entails. Others, such as Observer and Abstract Factory, are so widely used that upon reading the description a programmer will immediately recognize them.
Visitor is not like these other patterns: it takes a good amount of thinking to understand it, and it's use is limited to very specific cases. It is not used as often as many other design patterns, and, even so, some would argue that it is overused. As part of the learning process, programmers new to the concept of patterns often try to implement them in places where they are, at best, not helpful. Being a pattern that has limited applicability and, on top of that, is difficult to understand initially, Visitor may be the unwitting subject of such learning behavior more often than other patterns.
Forget about software for now and take a trip to the local supermarket with me. We'll grab a shopping cart and walk through the store picking up various items: a bunch of bananas as a healthy alternative to your typical afternoon snack, a bottle of wine for a friend's housewarming gift, and a pack of breath mints for tonight's big date at the Garlic Shack. After putting these three items into the cart, we go to the check-out to buy the items.
The cashier picks up the bananas, weighs them, and enters the item code found on a sticker on one of the bananas. The cost of the bananas is then added to the subtotal. Next, the cashier picks up the breath mints and scans the UPC code, adding the cost of the mints to the subtotal. Finally the cashier asks for your ID, checks it, verifies that you are at least 21. She then scans the UPC code on the bottle of wine, and the cost of the wine is added to your subtotal. The tax is then calculated, including an extra tax added onto the wine, and added to the subtotal to calculate the total.
Though you may not realize it, the cashier is a visitor. Let's look at it from this perspective. The cart is simply an object containing three other objects: bananas, breath mints, and wine. Though these four things share a context (i.e. they are all in a grocery store), there is no is-a relationship. A shopping cart is not a bunch of bananas. A bottle of wine is not a package of breath mints. We could say that all three items are all part of some category of inventory items, but we would be hard-pressed to impose that relationship onto the shopping cart. What we have is a sort of hierarchy of objects whose classes are not necessarily related.
Three different items contained in a shopping cart must be bought, and each of these items must be evaluated differently. The cashier knows the correct way to find the price and, possibly, evaluate the legality of the purchase in each case.
If This Was Software
Let's go over part of this story again, only this time we'll pretend that it's an executing program that implements the Visitor pattern.
Assuming the cart is filled, here is the line of code that instigates the purchase.
That code doesn't show much. Let's look at the definition of Accept() that ShoppingCart is using. (Depending on how the language is typed, the code below may require all the items in the cart to have a common base class or interface, and CheckoutVisitor is most certainly a subclass of Visitor.)
ShoppingCart.Accept(aVisitor) for each item in items this.Remove(item) item.Accept(aVisitor) aVisitor.VisitShoppingCart(this)
Now we're seeing a little more action. Upon being visited a shopping cart sends the visitor to each of the items contained in it (in this case the bananas, the mints, and the wine). It then calls VisitShoppingCart on the visitor, sending itself as the argument.
The inners workings of this is implementation specific, but the Accept() method in each of the items should resolve to code that looks something like this:
In the above code, VisitMyTypeOfObject() should be replace with vistBananas(), VisitMints(), or VisitWine(), or some variation thereof depending on your class structure. If your language supports it, you can implement this using overloading so that you can simply say aVisitor.VisitItem( this ), and the correct version of VisitItem() will be called at run time.
Now we're seeing where the real work is taking place. In our example, the above code causes the following methods to execute:
CheckoutVisitor.VisitBananas(someBananas) scale.Add(someBananas) register.PressKeys(someBananas.ItemCode) this.Bag(someBananas) CheckoutVisitor.VisitBreathMints(someBreathMints) upcScanner.Scan(someBreathMints.UPC) this.Bag(someBreathMints) CheckoutVisitor.VisitWine(aWineBottle) if (verifyAge(21)) upcScanner.Scan(aWineBottle.UPC) this.Bag(aWineBottle) CheckoutVisitor.VisitShoppingCart(aShoppingCart) this.CompleteTransaction() for each bag in bags aShoppingCart.Add(bag)
First of all, notice that we modeled real life here; it's not a program that you're likely to write unless you're into robotics. We simply modeled a process that we see on a regular basis for the purpose of understanding the visitor pattern.
There are a few reasons why Visitor works well here. The process involves objects with unrelated classes and differing interfaces. The cashier has been trained to know exactly what to do with each type of item. We can also imagine several other types of visitors that exist in grocery stores. Think of a stocker or a price tagger "visiting" shelves containing various items.
In real life, this isn't feasible, but pretend for a moment that you could actually teach each item how to administer its own purchase. A customer could unload a cart onto a counter, and each item would proceed to check the legality of its purchase, add its price to the subtotal, then hop into a bag. Each item would somehow have an algorithm encoded into it so that it could do this. What would happen if the store wanted to upgrade the cash registers? Each item in the store would have to have it's algorithm updated. That would be a lot of work. You can imagine that in an analogous situation in a true software system, each item class would require a small bit tweaking and a recompile at a minimum. Whereas, since everything already knows how to handle a visitor, making a new type of visitor would not even require the accepting classes to be recompiled.
Teaching the each item how it must be purchased, stocked, price-tagged, etc. would be the traditional object-oriented approach, and it would typically lead to a more clear and concise design. If the algorithms applied to these items are likely to change, however, the Visitor pattern becomes a more attractive solution. Conversely, if the types of items change on a regular basis, meaning that the visitors would constantly need updating to handle the different types of items, the Visitor pattern becomes a less attractive solution.
That Is All
Hopefully this serves to clarify the idea of Visitor somewhat. For real code examples and good guidelines as to when it should and should not be used, you should read the section on the Visitor pattern in Design Patterns.