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
- class VehicleVisitor; //forward reference
- class Vehicle
- {
- private:
- string owner;
- public:
- Vehicle(string in);
- void Accept(VehicleVisitor* v);
- };
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
- class VehicleVisitor; //forward reference
- class Vehicle
- {
- private:
- string owner;
- virtual void DoAccept(VehicleVisitor* v);
- public:
- Vehicle(string in);
- string GetOwner();
- void Accept(VehicleVisitor* v);
- };
- Vehicle::Vehicle(string in) : owner(in)
- {
- }
- string Vehicle::GetOwner()
- {
- return this->owner;
- }
- void Vehicle::Accept(VehicleVisitor* v)
- {
- this->DoAccept(v);
- }
- //Hook method. Not required to be overriden
- void Vehicle::DoAccept(VehicleVisitor* v)
- {
- }
Expand|Select|Wrap|Line Numbers
- class Automobile : public Vehicle
- {
- private:
- void DoAccept(VehicleVisitor* v);
- string LicenseNumber;
- public:
- Automobile(string owner, string license);
- string GetLicenseNumber();
- };
- Automobile::Automobile(string owner, string license)
- : Vehicle(owner), LicenseNumber(license)
- {
- }
- string Automobile::GetLicenseNumber()
- {
- return LicenseNumber;
- }
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
- class VehicleVisitor
- {
- public:
- virtual void VisitAutomobile(Automobile* c);
- protected:
- VehicleVisitor(); //only derived classes can create objects
- };
- VehicleVisitor::VehicleVisitor()
- {
- }
- //Hook method. Need not be implemented by all derived classes
- void VehicleVisitor::VisitAutomobile(Automobile* c)
- {
- }
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
- void Automobile::DoAccept(VehicleVisitor* v)
- {
- v->VisitAutomobile(this);
- }
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
- class AutomobileVisitor :public VehicleVisitor
- {
- public:
- void VisitAutomobile(Automobile* c);
- //Use the Car interface
- string GetLicenseNumber();
- //Use the Vehicle Interface
- string GetOwner();
- private:
- //The Automobile last visited;
- Automobile* theAutomobile;
- };
- void AutomobileVisitor::VisitAutomobile(Automobile*a)
- {
- this->theAutomobile = a; //save the Automobile*
- }
- string AutomobileVisitor::GetLicenseNumber()
- {
- return this->theAutomobile->GetLicenseNumber();
- }
- string AutomobileVisitor::GetOwner()
- {
- return this->theAutomobile->GetOwner();
- }
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
- int main()
- {
- Vehicle* ptr = new Automobile("John Smith", "XYZ-123");
- AutomobileVisitor* v = new AutomobileVisitor;
- ptr->Accept(v);
- cout << v->GetOwner() << endl; //from Vehicle
- cout << v->GetLicenseNumber() << endl; //from Automobile
- }
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
- class AutomobileArchive :public VehicleVisitor
- {
- public:
- AutomobileArchive(string filename);
- void VisitAutomobile(Automobile* c);
- //Use the Car interface
- string GetLicenseNumber();
- //Use the Vehicle Interface
- string GetOwner();
- //Added methids for Automobile
- void Serialize();
- fstream& GetArchive();
- private:
- //The Automobile last visited;
- Automobile* theAutomobile;
- fstream archive;
- };
- AutomobileArchive::AutomobileArchive(string filename)
- {
- archive.open(filename.c_str(), ios_base::out | ios_base::app);
- }
- void AutomobileArchive::VisitAutomobile(Automobile*a)
- {
- this->theAutomobile = a;
- }
- string AutomobileArchive::GetLicenseNumber()
- {
- return this->theAutomobile->GetLicenseNumber();
- }
- string AutomobileArchive::GetOwner()
- {
- return this->theAutomobile->GetOwner();
- }
- void AutomobileArchive::Serialize()
- {
- string temp = this->GetOwner();
- temp += '\n';
- archive.write(temp.c_str(), temp.size());
- temp = this->GetLicenseNumber();
- temp += '\n';
- archive.write(temp.c_str(), temp.size());
- }
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
- int main()
- {
- Vehicle* ptr = new Automobile("John Smith", "XYZ-123");
- AutomobileVisitor* v = new AutomobileVisitor;
- ptr->Accept(v);
- cout << v->GetOwner() << endl; //from Vehicle
- cout << v->GetLicenseNumber() << endl; //from Automobile
- AutomobileArchive save("C:\\scratch\\instructor\\archive.txt");
- ptr->Accept(&save);
- save.Serialize();
- }
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