473,321 Members | 1,622 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes and contribute your articles to a community of 473,321 developers and data experts.

Design Patterns: Visitor

weaknessforcats
9,208 Expert Mod 8TB
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
0 11574

Sign in to post your reply or Sign up for a free account.

Similar topics

9
by: Patchwork | last post by:
Hi Everyone, I have a design related question (in C++) that I am hoping someone can help me with. It is related to my previous post but since it was pointed out that I was more or less asking...
11
by: FluffyCat | last post by:
In Febraury - April of 2002 I put together in Java examples of all 23 of the classic "Gang Of Four" design patterns for my website. Partly I wanted to get a better understanding of those patterns....
12
by: Mike | last post by:
I want to provide an abstract class to derive all nodes from. In the example below Parent in the base class is of Type Node, but in the custom class I want Parent to return a Type CustomNode. When...
2
by: Wavemaker | last post by:
I've been playing with C# v2.0 today, and having quite a bit of fun. The new version has added iterators. The iterators are coded directly into the class to be iterated. For example: public...
12
by: FluffyCat | last post by:
New on November 28, 2005 for www.FluffyCat.com PHP 5 Design Pattern Examples - the Visitor Pattern. In the Visitor pattern, one class calls a function in another class and passes an instance of...
12
by: Jean-pierre Martineau | last post by:
how apply design patterns to c ?
22
by: Krivenok Dmitry | last post by:
Hello All! I am trying to implement my own Design Patterns Library. I have read the following documentation about Observer Pattern: 1) Design Patterns by GoF Classic description of Observer....
1
by: JosAH | last post by:
Greetings, this week we let go of all that algebraic stuff and concentrate a bit more on what object oriented programming is all about. Java claims to support OO, so why not use it? In this...
2
by: gas | last post by:
Hi So, I have the following problem: I have to store several types of Cells, (call them A, B, ...), where the number of Cell types is going to increase as we continue development. Among...
0
by: DolphinDB | last post by:
Tired of spending countless mintues downsampling your data? Look no further! In this article, you’ll learn how to efficiently downsample 6.48 billion high-frequency records to 61 million...
0
isladogs
by: isladogs | last post by:
The next Access Europe meeting will be on Wednesday 6 Mar 2024 starting at 18:00 UK time (6PM UTC) and finishing at about 19:15 (7.15PM). In this month's session, we are pleased to welcome back...
1
isladogs
by: isladogs | last post by:
The next Access Europe meeting will be on Wednesday 6 Mar 2024 starting at 18:00 UK time (6PM UTC) and finishing at about 19:15 (7.15PM). In this month's session, we are pleased to welcome back...
0
by: ArrayDB | last post by:
The error message I've encountered is; ERROR:root:Error generating model response: exception: access violation writing 0x0000000000005140, which seems to be indicative of an access violation...
1
by: PapaRatzi | last post by:
Hello, I am teaching myself MS Access forms design and Visual Basic. I've created a table to capture a list of Top 30 singles and forms to capture new entries. The final step is a form (unbound)...
1
by: CloudSolutions | last post by:
Introduction: For many beginners and individual users, requiring a credit card and email registration may pose a barrier when starting to use cloud servers. However, some cloud server providers now...
1
by: Shællîpôpï 09 | last post by:
If u are using a keypad phone, how do u turn on JavaScript, to access features like WhatsApp, Facebook, Instagram....
0
by: Faith0G | last post by:
I am starting a new it consulting business and it's been a while since I setup a new website. Is wordpress still the best web based software for hosting a 5 page website? The webpages will be...
0
isladogs
by: isladogs | last post by:
The next Access Europe User Group meeting will be on Wednesday 3 Apr 2024 starting at 18:00 UK time (6PM UTC+1) and finishing by 19:30 (7.30PM). In this session, we are pleased to welcome former...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.