Because visitors are usually associated with composites, we'll use the Equipment classes defined in the Sample Code of Composite to illustrate the Visitor pattern. We will use Visitor to define operations for computing the inventory of materials and the total cost for a piece of equipment. The Equipment classes are so simple that using Visitor isn't really necessary, but they make it easy to see what's involved in implementing the pattern.
Here again is the Equipment class from Composite. We've augmented it with an Accept operation to let it work with a visitor.
The Equipment operations return the attributes of a piece of equipment, such as its power consumption and cost. Subclasses redefine these operations appropriately for specific types of equipment (e.g., a chassis, drives, and planar boards).class Equipment { public: virtual ~Equipment(); const char* Name() { return _name; } virtual Watt Power(); virtual Currency NetPrice(); virtual Currency DiscountPrice(); virtual void Accept(EquipmentVisitor&); protected: Equipment(const char*); private: const char* _name; };
The abstract class for all visitors of equipment has a virtual function for each subclass of equipment, as shown next. All of the virtual functions do nothing by default.
Equipment subclasses define Accept in basically the same way: It calls the EquipmentVisitor operation that corresponds to the class that received the Accept request, like this:class EquipmentVisitor { public: virtual ~EquipmentVisitor(); virtual void VisitFloppyDisk(FloppyDisk*); virtual void VisitCard(Card*); virtual void VisitChassis(Chassis*); virtual void VisitBus(Bus*); // and so on for other concrete subclasses of Equipment protected: EquipmentVisitor(); };
Equipment that contains other equipment (in particular, subclasses of CompositeEquipment in the Composite pattern) implements Accept by iterating over their children and calling Accept on each of them. They then call the Visit operation on themselves. For example, Chassis::Accept could traverse all the parts in the chassis as follows:void FloppyDisk::Accept (EquipmentVisitor& visitor) { visitor.VisitFloppyDisk(this); }
Subclasses of EquipmentVisitor define particular algorithms over the equipment structure. The PricingVisitor computes the cost of the equipment structure. It computes the net price of all simple equipment (e.g., floppies) and the discount price of all composite equipment (e.g., chassis and buses).void Chassis::Accept (EquipmentVisitor& visitor) { for ( ListIterator i(_parts); !i.IsDone(); i.Next() ) { i.CurrentItem()->Accept(visitor); } visitor.VisitChassis(this); }
PricingVisitor will compute the total cost of all nodes in the equipment structure. Note that PricingVisitor chooses the appropriate pricing policy for a class of equipment by dispatching to the corresponding member function. What's more, we can change the pricing policy of an equipment structure just by changing the PricingVisitor class.class PricingVisitor : public EquipmentVisitor { public: PricingVisitor(); Currency& GetTotalPrice(); virtual void VisitFloppyDisk(FloppyDisk*); virtual void VisitCard(Card*); virtual void VisitChassis(Chassis*); virtual void VisitBus(Bus*); // ... private: Currency _total; }; void PricingVisitor::VisitFloppyDisk (FloppyDisk* e) { _total += e->NetPrice(); } void PricingVisitor::VisitChassis (Chassis* e) { _total += e->DiscountPrice(); }
We can define a visitor for computing inventory like this:
The InventoryVisitor accumulates the totals for each type of equipment in the object structure. InventoryVisitor uses an Inventory class that defines an interface for adding equipment (which we won't bother defining here).class InventoryVisitor : public EquipmentVisitor { public: InventoryVisitor(); Inventory& GetInventory(); virtual void VisitFloppyDisk(FloppyDisk*); virtual void VisitCard(Card*); virtual void VisitChassis(Chassis*); virtual void VisitBus(Bus*); // ... private: Inventory _inventory; };
Here's how we can use an InventoryVisitor on an equipment structure:void InventoryVisitor::VisitFloppyDisk (FloppyDisk* e) { _inventory.Accumulate(e); } void InventoryVisitor::VisitChassis (Chassis* e) { _inventory.Accumulate(e); }
Now we'll show how to implement the Smalltalk example from the Interpreter pattern with the Visitor pattern. Like the previous example, this one is so small that Visitor probably won't buy us much, but it provides a good illustration of how to use the pattern. Further, it illustrates a situation in which iteration is the visitor's responsibility.Equipment* component; InventoryVisitor visitor; component->Accept(visitor); cout << "Inventory " << component->Name() << visitor.GetInventory();
The object structure (regular expressions) is made of four classes, and all of them have an accept: method that takes the visitor as an argument. In class SequenceExpression, the accept: method is
In class RepeatExpression, the accept: method sends the visitRepeat: message. In class AlternationExpression, it sends the visitAlternation: message. In class LiteralExpression, it sends the visitLiteral: message.accept: aVisitor ^ aVisitor visitSequence: self
The four classes also must have accessing functions that the visitor can use. For SequenceExpression these are expression1 and expression2; for AlternationExpression these are alternative1 and alternative2; for RepeatExpression it is repetition; and for LiteralExpression these are components. The ConcreteVisitor class is REMatchingVisitor. It is responsible for the traversal because its traversal algorithm is irregular. The biggest irregularity is that a RepeatExpression will repeatedly traverse its component. The class REMatchingVisitor has an instance variable inputState. Its methods are essentially the same as the match: methods of the expression classes in the Interpreter pattern except they replace the argument named inputState with the expression node being matched. However, they still return the set of streams that the expression would match to identify the current state.
visitSequence: sequenceExp inputState := sequenceExp expression1 accept: self. ^ sequenceExp expression2 accept: self. visitRepeat: repeatExp | finalState | finalState := inputState copy. [inputState isEmpty] whileFalse: [inputState := repeatExp repetition accept: self. finalState addAll: inputState]. ^ finalState visitAlternation: alternateExp | finalState originalState | originalState := inputState. finalState := alternateExp alternative1 accept: self. inputState := originalState. finalState addAll: (alternateExp alternative2 accept: self). ^ finalState visitLiteral: literalExp | finalState tStream | finalState := Set new. inputState do: [:stream | tStream := stream copy. (tStream nextAvailable: literalExp components size ) = literalExp components ifTrue: [finalState add: tStream] ]. ^ finalState