By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
457,978 Members | 1,093 Online
Bytes IT Community
Submit an Article
Got Smarts?
Share your bits of IT knowledge by writing an article on Bytes.

Design Patterns: Visitor

weaknessforcats
Expert Mod 5K+
P: 9,197
Design Patterns: Visitor

Introduction
Polymorphism requires a class hierarchy where the interface to the hierarchy is in the base class. Virtual functions allow derived classes to override base class functions. Applications using polymorphism typically have functions with base class pointers or references as arguments. Then derived objects are created and used as arguments to these functions. Inside the function, only the base class methods of the derived object can be called.

This requires a homomorphic hierarchy. That is, the base class and the derived class all have the same methods.

However, in the real world, it seldom happens that the base class and all derived classes (including those that won't be known until years in the future) can all have the same methods.

Worse, it can happen that a method is required temporarily. Like a cost method for an Automobile class that only needs to calculate the sales tax in the state of Virginia in the year that the automobile was sold as part of a one-time analysis. Even worse, the year of sale and the tax table are not in the Automobile class.

Then there's the new requirement for a method that was not built into the original hierarchy. Perhaps, a Serialize() method to move objects into and out of a database which can't use the operator<< inserters of the derived classes due to incompatible formats.

Put all this together and imagine an already installed customer base of many thousands and you begin to get an idea of a running C++ application.

Changes, like the ones outlined above, need to be made. But in making them, the original classes cannot be changed because a) the change does not apply to all classes, b) the change is temporary, c) the customer base cannot be re-installed d) any new functions can have only base class pointer or reference arguments to be compatible with the rest of the application.

A dead end is dynamic casting.

Dynamic casting (RTTI) is a poor solution because RTTI itself is expensive and it requires that code containing the derived class name be installed. As new derived classes are added, or perhaps as classes disappear, this code needs to be changed and that requires a re-install of the customer base, which is not allowed.


The Visitor design pattern addresses these issues.

Implementing Visitor
Visitor must be included as part of the original hierarchy design. The base class of the original hierarchy requires an Accept() method that takes a pointer or reference to a VehicleVisitor base class.

Below is a base class Vehicle with an Accept() method for a VehicleVisitor. Since the Vehicle::Accept() method is part of the base class interface, it is not virtual. This is done to separate the interface from the implementation. That is, the derived class can override part of, or all of, the Vehicle::Accept() method processing but the derived class cannot override the base class method itself and do something else entirely.

Expand|Select|Wrap|Line Numbers
  1. class VehicleVisitor;  //forward reference
  2. class Vehicle
  3. {
  4.     private:
  5.         string owner;
  6.  
  7.     public:
  8.         Vehicle(string in);
  9.         void Accept(VehicleVisitor* v);
  10.  
  11. };
  12.  
Instead, Vehicle::Accept() will be implemented as a hook. A hook is a method does nothing. That is, a derived class can override Accept() to obtain the VehicleVisitor pointer or reference but it is not required to do so. This frees the derived class from having methods it does not support just to satisfy C++.

This is accomplished in the example below using a private Vehicle::DoAccept() method. This is the hook method. If the derived class does not override Vehicle::DoAccept(), then the Vehicle::DoAccept() will just return without doing anything.

Otherwise, the derived class can implement Derived::DoAccept() and obtain the VehicleVisitor pointer or reference.

Expand|Select|Wrap|Line Numbers
  1. class VehicleVisitor;  //forward reference
  2. class Vehicle
  3. {
  4.     private:
  5.         string owner;
  6.         virtual void DoAccept(VehicleVisitor* v);
  7.  
  8.     public:
  9.         Vehicle(string in);
  10.         string GetOwner();
  11.         void Accept(VehicleVisitor* v);
  12.  
  13. };
  14.  
  15. Vehicle::Vehicle(string in) : owner(in)
  16. {
  17.  
  18. }
  19. string Vehicle::GetOwner()
  20. {
  21.     return this->owner;
  22. }
  23. void Vehicle::Accept(VehicleVisitor* v)
  24. {
  25.         this->DoAccept(v);
  26. }
  27. //Hook method. Not required to be overriden
  28. void Vehicle::DoAccept(VehicleVisitor* v)
  29. {
  30.  
  31. }
  32.  
The Automobile class can now be derived from Vehicle. In this example, the Automobile has a license number in addition to an owner. This class will override Vehicle::DoAccept() to obtain the VehicleVisitor pointer.

Expand|Select|Wrap|Line Numbers
  1. class Automobile : public Vehicle
  2. {
  3.     private:
  4.         void DoAccept(VehicleVisitor* v);
  5.         string LicenseNumber;
  6.     public:
  7.         Automobile(string owner, string license);
  8.         string GetLicenseNumber();
  9.  
  10. };
  11. Automobile::Automobile(string owner, string license)
  12.      : Vehicle(owner), LicenseNumber(license)
  13. {
  14.  
  15. }
  16. string Automobile::GetLicenseNumber()
  17. {
  18.         return LicenseNumber;
  19. }
  20.  
At this time the VehicleVisitor hierarchy needs to be defined. This hierarchy will parallel the Vehicle hierarchy. Where there is a Vehicle base class, there will be VehicleVisitor base class. Where there is an Automobile derived class, there will be an AutomobileVisitor derived class, etc.

Not all VehicleVisitor objects will need a VisitAutomobile() method, so this method is also implemented as a hook. If not overridden, it does nothing at all.
Expand|Select|Wrap|Line Numbers
  1. class VehicleVisitor
  2. {
  3.     public:
  4.         virtual void VisitAutomobile(Automobile* c);
  5.  
  6.     protected:
  7.         VehicleVisitor();  //only derived classes can create objects
  8.  
  9.  
  10. };
  11. VehicleVisitor::VehicleVisitor()
  12. {
  13.  
  14. }
  15. //Hook method. Need not be implemented by all derived classes
  16. void VehicleVisitor::VisitAutomobile(Automobile* c)
  17. {
  18.  
  19. }
  20.  
  21.  
The Automobile::DoAccept() can be implemented now that the VehicleVisitor is known to have a VisitAutomobile() method. The this pointer below is an Automobile* because this is an Automobile method.

Here is the heart of the pattern. A call to a VehicleVisitor method is being made with a derived class pointer. That means VehicleVisitor::VisitAutomobile has a pointer to the derived object. That is, an Automobile*.

Expand|Select|Wrap|Line Numbers
  1. void Automobile::DoAccept(VehicleVisitor* v)
  2. {
  3.         v->VisitAutomobile(this);
  4.  
  5. }
  6.  
All that remains is to derive an AutomobileVisitor from VehicleVisitor and implement the hook method VisitAutomobile(). This class is shown below.

The VisitAutomobile() method saves the Automobile* used as the argument. This pointer can be used by AutomobileVisitor to call methods on Automobile.

The other AutomobileVisitor methods are wrappers of the Automobile methods. They use the pointer obtained by VisitAutomobile to call the corresponding methods on the Automobile object.

You may add additional methods (and data) to AutomobileVisitor in addition to the wrapper Automobile methods. This effectively expands the Automobile class without changing the Automobile class.
Expand|Select|Wrap|Line Numbers
  1. class AutomobileVisitor :public VehicleVisitor
  2. {
  3.     public:
  4.         void VisitAutomobile(Automobile* c);
  5.  
  6.         //Use the Car interface
  7.         string GetLicenseNumber();
  8.         //Use the Vehicle Interface
  9.         string GetOwner();
  10.  
  11.     private:
  12.         //The Automobile last visited;
  13.         Automobile* theAutomobile;    
  14.  
  15. };
  16. void AutomobileVisitor::VisitAutomobile(Automobile*a)
  17. {
  18.     this->theAutomobile = a;  //save the Automobile*
  19. }
  20. string AutomobileVisitor::GetLicenseNumber()
  21. {
  22.     return this->theAutomobile->GetLicenseNumber();
  23. }
  24. string AutomobileVisitor::GetOwner()
  25. {
  26.     return this->theAutomobile->GetOwner();
  27. }
  28.  
Below is a small driver program. Here an Automobile object is created and kept as a Vehicle*. Next, an AutomobileVisitor is created and its address used on the Vehicle::Accept(). Recall that Vehicle::Accept() calls Vehicle::DoAccept() and that this function is overridden by Automobile::DoAccept() so it is Automobile::DoAccept() that is really called. In turn, Automobile::DoAccept() calls VehicleVisitor::VisitAutomobile() which has been overridden by AutomobileVisitor::VisitAutomobile() and it is this function that is really called.

The end result is the AutomobileVisitor object has acquired the address of the Automobile object. It then uses that address to call back to Automobile methods. The requirement that Automobile methods also be declared in Vehicle has been removed.

The Automobile can participate as a Vehicle where appropriate and as an Automobile otherwise. Where added methods are needed, just create an AutomobileVisitor object with the added methods and use its address to call Vehicle::Accept(). Then use the AutomobileVisitor as an expanded Automobile.

Pay special attention that only a Vehicle* was used in main() and that there are no typecasts or RTTI anywhere in this pattern.

Expand|Select|Wrap|Line Numbers
  1. int main()
  2. {
  3.     Vehicle* ptr = new Automobile("John Smith", "XYZ-123");
  4.     AutomobileVisitor* v = new AutomobileVisitor;
  5.     ptr->Accept(v);
  6.     cout << v->GetOwner() << endl;                  //from Vehicle
  7.     cout << v->GetLicenseNumber() << endl;    //from Automobile
  8.  
  9.  
  10. }
  11.  
Using Visitor To Add Methods to a Class
Imagine a requirement has been made to serialize a Vehicle to disc. There is no Serialize() method on Vehicle. A method is required instead of the << inserter because the file format is different from the display format. In this case, the Automobile needs to be written to disc as text with each data member in its own record. That means both the owner and the license number have to be written.

Using the code examples above, a AutomobileArchive class can be written as an AutomobileVisitor that has a Serialize method.

Expand|Select|Wrap|Line Numbers
  1. class AutomobileArchive :public VehicleVisitor
  2. {
  3.     public:
  4.         AutomobileArchive(string filename);
  5.         void VisitAutomobile(Automobile* c);
  6.  
  7.         //Use the Car interface
  8.         string GetLicenseNumber();
  9.         //Use the Vehicle Interface
  10.         string GetOwner();    
  11.         //Added methids for Automobile
  12.         void Serialize();
  13.         fstream& GetArchive();
  14.  
  15.     private:
  16.         //The Automobile last visited;
  17.         Automobile* theAutomobile;    
  18.         fstream archive;
  19.  
  20. };
  21. AutomobileArchive::AutomobileArchive(string filename)
  22. {
  23.     archive.open(filename.c_str(), ios_base::out | ios_base::app);
  24. }
  25. void AutomobileArchive::VisitAutomobile(Automobile*a)
  26. {
  27.  
  28.     this->theAutomobile = a;
  29. }
  30. string AutomobileArchive::GetLicenseNumber()
  31. {
  32.     return this->theAutomobile->GetLicenseNumber();
  33. }
  34. string AutomobileArchive::GetOwner()
  35. {
  36.     return this->theAutomobile->GetOwner();
  37. }
  38. void AutomobileArchive::Serialize()
  39. {
  40.     string temp = this->GetOwner();
  41.     temp += '\n';
  42.     archive.write(temp.c_str(), temp.size());
  43.     temp = this->GetLicenseNumber();
  44.     temp += '\n';
  45.     archive.write(temp.c_str(), temp.size());
  46. }
  47.  
  48.  
  49.  
  50.  
The above AutomobileArchive is a VehicleVisitor. A constructor has been added to open the disc file in append mode.

A Serialize() method has been added to obtain both the owner and the license number from the Automobile object and to append a \n to each of these after which the two strings are written to disc.

The driver program has been expanded to include use of this new Visitor:
Expand|Select|Wrap|Line Numbers
  1. int main()
  2. {
  3.     Vehicle* ptr = new Automobile("John Smith", "XYZ-123");
  4.     AutomobileVisitor* v = new AutomobileVisitor;
  5.     ptr->Accept(v);
  6.     cout << v->GetOwner() << endl;                  //from Vehicle
  7.     cout << v->GetLicenseNumber() << endl;    //from Automobile
  8.  
  9.     AutomobileArchive  save("C:\\scratch\\instructor\\archive.txt");
  10.     ptr->Accept(&save);
  11.     save.Serialize();
  12.  
  13.  
  14. }
  15.  
As you can see, all that is required is to create the AutomobileArchive object and visit the Vehicle* with it. After the AutomobileVisitor has been accepted, the Serialize method() can be used to save the Automobile to disc. Where there are many vehicle objects to be saved, each must be visited and then they can be serialized to disc. The code above would need to be replicated for the various kinds of vehicles.

This example is presented for concept only. Details have been omitted for the serialization of different kinds of vehicles.

Using Handles
This examples in this article use pointers since pointer syntax is commonly understood. However, it is recommended in a real application that handles be used. You should refer to the article on Handles in the C/C++ Articles section.

Further Information
Refer to the book Design Patterns by Erich Fromm, et al, Addison-Wesley 1994.

This article shows only the conceptual basis of the Visitor pattern but not motivations and ramifications of using this pattern.

Copyright 2007 Buchmiller Technical Associates North Bend WA USA
Jul 9 '07 #1
Share this Article
Share on Google+