Design Patterns: Visitor

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • weaknessforcats
    Recognized Expert Expert
    • Mar 2007
    • 9214

    Design Patterns: Visitor

    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.

    [code=cpp]
    class VehicleVisitor; //forward reference
    class Vehicle
    {
    private:
    string owner;

    public:
    Vehicle(string in);
    void Accept(VehicleV isitor* v);

    };
    [/code]
    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::DoAcce pt() method. This is the hook method. If the derived class does not override Vehicle::DoAcce pt(), then the Vehicle::DoAcce pt() will just return without doing anything.

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

    [code=cpp]
    class VehicleVisitor; //forward reference
    class Vehicle
    {
    private:
    string owner;
    virtual void DoAccept(Vehicl eVisitor* v);

    public:
    Vehicle(string in);
    string GetOwner();
    void Accept(VehicleV isitor* v);

    };

    Vehicle::Vehicl e(string in) : owner(in)
    {

    }
    string Vehicle::GetOwn er()
    {
    return this->owner;
    }
    void Vehicle::Accept (VehicleVisitor * v)
    {
    this->DoAccept(v);
    }
    //Hook method. Not required to be overriden
    void Vehicle::DoAcce pt(VehicleVisit or* v)
    {

    }
    [/code]

    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::DoAcce pt() to obtain the VehicleVisitor pointer.

    [code=cpp]
    class Automobile : public Vehicle
    {
    private:
    void DoAccept(Vehicl eVisitor* v);
    string LicenseNumber;
    public:
    Automobile(stri ng owner, string license);
    string GetLicenseNumbe r();

    };
    Automobile::Aut omobile(string owner, string license)
    : Vehicle(owner), LicenseNumber(l icense)
    {

    }
    string Automobile::Get LicenseNumber()
    {
    return LicenseNumber;
    }
    [/code]

    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 AutomobileVisit or 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.
    [code=cpp]
    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: :VisitAutomobil e(Automobile* c)
    {

    }

    [/code]
    The Automobile::DoA ccept() 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: :VisitAutomobil e has a pointer to the derived object. That is, an Automobile*.

    [code=cpp]
    void Automobile::DoA ccept(VehicleVi sitor* v)
    {
    v->VisitAutomobil e(this);

    }
    [/code]
    All that remains is to derive an AutomobileVisit or 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 AutomobileVisit or to call methods on Automobile.

    The other AutomobileVisit or 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 AutomobileVisit or in addition to the wrapper Automobile methods. This effectively expands the Automobile class without changing the Automobile class.
    [code=cpp]
    class AutomobileVisit or :public VehicleVisitor
    {
    public:
    void VisitAutomobile (Automobile* c);

    //Use the Car interface
    string GetLicenseNumbe r();
    //Use the Vehicle Interface
    string GetOwner();

    private:
    //The Automobile last visited;
    Automobile* theAutomobile;

    };
    void AutomobileVisit or::VisitAutomo bile(Automobile *a)
    {
    this->theAutomobil e = a; //save the Automobile*
    }
    string AutomobileVisit or::GetLicenseN umber()
    {
    return this->theAutomobil e->GetLicenseNumb er();
    }
    string AutomobileVisit or::GetOwner()
    {
    return this->theAutomobil e->GetOwner();
    }
    [/code]
    Below is a small driver program. Here an Automobile object is created and kept as a Vehicle*. Next, an AutomobileVisit or is created and its address used on the Vehicle::Accept (). Recall that Vehicle::Accept () calls Vehicle::DoAcce pt() and that this function is overridden by Automobile::DoA ccept() so it is Automobile::DoA ccept() that is really called. In turn, Automobile::DoA ccept() calls VehicleVisitor: :VisitAutomobil e() which has been overridden by AutomobileVisit or::VisitAutomo bile() and it is this function that is really called.

    The end result is the AutomobileVisit or 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 AutomobileVisit or object with the added methods and use its address to call Vehicle::Accept (). Then use the AutomobileVisit or 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.

    [code=cpp]
    int main()
    {
    Vehicle* ptr = new Automobile("Joh n Smith", "XYZ-123");
    AutomobileVisit or* v = new AutomobileVisit or;
    ptr->Accept(v);
    cout << v->GetOwner() << endl; //from Vehicle
    cout << v->GetLicenseNumb er() << endl; //from Automobile


    }
    [/code]
    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 AutomobileArchi ve class can be written as an AutomobileVisit or that has a Serialize method.

    [code=cpp]
    class AutomobileArchi ve :public VehicleVisitor
    {
    public:
    AutomobileArchi ve(string filename);
    void VisitAutomobile (Automobile* c);

    //Use the Car interface
    string GetLicenseNumbe r();
    //Use the Vehicle Interface
    string GetOwner();
    //Added methids for Automobile
    void Serialize();
    fstream& GetArchive();

    private:
    //The Automobile last visited;
    Automobile* theAutomobile;
    fstream archive;

    };
    AutomobileArchi ve::AutomobileA rchive(string filename)
    {
    archive.open(fi lename.c_str(), ios_base::out | ios_base::app);
    }
    void AutomobileArchi ve::VisitAutomo bile(Automobile *a)
    {

    this->theAutomobil e = a;
    }
    string AutomobileArchi ve::GetLicenseN umber()
    {
    return this->theAutomobil e->GetLicenseNumb er();
    }
    string AutomobileArchi ve::GetOwner()
    {
    return this->theAutomobil e->GetOwner();
    }
    void AutomobileArchi ve::Serialize()
    {
    string temp = this->GetOwner();
    temp += '\n';
    archive.write(t emp.c_str(), temp.size());
    temp = this->GetLicenseNumb er();
    temp += '\n';
    archive.write(t emp.c_str(), temp.size());
    }



    [/code]
    The above AutomobileArchi ve 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:
    [code=cpp]
    int main()
    {
    Vehicle* ptr = new Automobile("Joh n Smith", "XYZ-123");
    AutomobileVisit or* v = new AutomobileVisit or;
    ptr->Accept(v);
    cout << v->GetOwner() << endl; //from Vehicle
    cout << v->GetLicenseNumb er() << endl; //from Automobile

    AutomobileArchi ve save("C:\\scrat ch\\instructor\ \archive.txt");
    ptr->Accept(&save );
    save.Serialize( );


    }
    [/code]
    As you can see, all that is required is to create the AutomobileArchi ve object and visit the Vehicle* with it. After the AutomobileVisit or 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
    Last edited by weaknessforcats; Jul 9 '07, 04:40 PM. Reason: Fix boldface tag
Working...