设计模式:访客

设计模式:访客

介绍

多态需要一个类层次结构,其中层次结构的接口在基类中。 虚函数允许派生类覆盖基类函数。 使用多态的应用程序通常具有将基类指针或引用作为参数的函数。 然后创建派生对象,并将其用作这些函数的参数。 在函数内部,只能调用派生对象的基类方法。

这需要一个

同构层次 。 也就是说,基类和派生类都具有相同的方法。

但是,在现实世界中,很少发生基类和所有派生类(包括那些直到几年后才知道的派生类)都具有相同的方法的情况。

更糟糕的是,可能会临时需要某种方法。 就像“汽车”类别的成本方法一样,该汽车只需要计算汽车销售当年的销售税,作为一次分析的一部分。 更糟糕的是,销售年份和税表不在“汽车”类别中。

然后,有一个新的要求,即方法不是内置在原始层次结构中的。 也许是Serialize()方法,用于将对象移入和移出数据库,由于格式不兼容,该方法不能使用派生类的operator <<插入器。

将所有这些放在一起,想象一下已经有成千上万的客户群,您就会开始了解正在运行的C ++应用程序。

像上面概述的那样,需要进行更改。 但是在制作它们时,不能更改原始类,因为a)更改并不适用于所有类,b)更改是暂时的,c)无法重新安装客户基础d)任何新功能只能具有基类指针或引用参数,以与应用程序的其余部分兼容。

死角是动态铸造。

动态转换(RTTI)是一个较差的解决方案,因为RTTI本身很昂贵,并且需要安装包含派生类名的代码。 随着添加新的派生类,或者随着类的消失,需要更改此代码,并且这需要重新安装客户群,这是不允许的。

访客设计模式解决了这些问题。

实施访客

访客必须包含在原始层次结构设计中。 原始层次结构的基类需要一个Accept()方法,该方法接受指向VehicleVisitor基类的指针或引用。

以下是具有VehicleVisitor的Accept()方法的基类Vehicle。 由于Vehicle :: Accept()方法是基类接口的一部分,因此它不是虚拟的。 这样做是为了将接口与实现分开。 也就是说,派生类可以覆盖Vehicle :: Accept()方法处理的一部分或全部,但派生类不能覆盖基类方法本身,并且可以完全执行其他操作。


class VehicleVisitor;  //forward reference
class Vehicle
{
    private:
        string owner; 
    public:
        Vehicle(string in);
        void Accept(VehicleVisitor* v); 
}; 
而是将Vehicle :: Accept()实现为hook 。 钩子是一种无能为力的方法。 也就是说,派生类可以重写Accept()以获取VehicleVisitor指针或引用,但并非必须这样做。 这使派生类摆脱了不仅仅满足C ++的方法。

在下面的示例中,这是使用私有Vehicle :: DoAccept()方法完成的。 这是挂钩方法。 如果派生类未覆盖Vehicle :: DoAccept(),则Vehicle :: DoAccept()将不做任何事情而返回。

否则,派生类可以实现Derived :: DoAccept()并获取VehicleVisitor指针或引用。


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)
{ 
} 
现在可以从Vehicle派生Automobile类。 在此示例中,汽车除拥有者外还具有许可证编号。 此类将重写Vehicle :: DoAccept()以获取VehicleVisitor指针。

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;
} 
此时,需要定义VehicleVisitor层次结构。 此层次结构将与Vehicle层次结构平行。 如果存在Vehicle基类,则将有VehicleVisitor基类。 如果有汽车类的派生类,将有汽车汽车类的派生类,等等。

并非所有VehicleVisitor对象都需要VisitVimobile()方法,因此该方法也实现为钩子。 如果不被覆盖,它什么也不做。


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)
{ 
}  
由于已知VehicleVisitor具有VisitAutomobile()方法,因此可以实现Automobile :: DoAccept()。 下面的this指针是Automobile *,因为这是Automobile方法。

这是模式的核心。 正在使用派生的类指针对VehicleVisitor方法进行调用。 这意味着VehicleVisitor :: VisitAutomobile具有指向派生对象的指针。 即,汽车*。


void Automobile::DoAccept(VehicleVisitor* v)
{
        v->VisitAutomobile(this); 
} 
剩下的就是从VehicleVisitor派生AutomobileVisitor并实现挂钩方法VisitAutomobile()。 此类显示如下。

VisitAutomobile()方法保存将Automobile *用作参数。 AutomobileVisitor可以使用此指针来调用Automobile上的方法。

其他AutomobileVisitor方法是Automobile方法的包装。 他们使用VisitAutomobile获得的指针来调用Automobile对象上的相应方法。

除了包装器Automobile方法之外,您还可以向AutomobileVisitor添加其他方法(和数据)。 这有效地扩展了汽车类,而无需更改汽车类。


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();
} 
下面是一个小型驱动程序。 在此,将创建一个汽车对象并将其保留为汽车*。 接下来,创建一个AutomobileVisitor,并将其地址用于Vehicle :: Accept()。 回想一下Vehicle :: Accept()调用Vehicle :: DoAccept(),并且此函数被Automobile :: DoAccept()覆盖,因此真正调用的是Automobile :: DoAccept()。 反过来,Automobile :: DoAccept()调用VehicleVisitor :: VisitAutomobile()(已被AutomobileVisitor :: VisitAutomobile()覆盖),并且真正调用了此函数。

最终结果是AutomobileVisitor对象已获取Automobile对象的地址。 然后,它将使用该地址来调用Automobile方法。 删除了在车辆中也声明汽车方法的要求。

在适当的情况下,汽车可以作为汽车参加,否则可以作为汽车参加。 需要添加方法的地方,只需使用添加的方法创建一个AutomobileVisitor对象,并使用其地址来调用Vehicle :: Accept()。 然后将AutomobileVisitor用作扩展的Automobile。

请特别注意,main()中仅使用了Vehicle *,并且此模式中的任何地方都没有类型转换或RTTI。


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  
} 
使用访问者向类添加方法

想象一下,已经要求将车辆序列化为光盘。 Vehicle上没有Seri​​alize()方法。 因为文件格式与显示格式不同,所以需要一种方法来代替<< <<插入器。 在这种情况下,汽车需要以文本形式写入磁盘,并且每个数据成员都在其自己的记录中。 这意味着必须同时写出所有者和许可证号。

使用上面的代码示例,可以将AutomobileArchive类编写为具有Seri​​alize方法的AutomobileVisitor。


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());
}    
上面的AutomobileArchive是VehicleVisitor。 添加了一个构造函数以以附加模式打开光盘文件。

添加了Serialize()方法,以从Automobile对象获得所有者和许可证号,并在每一个后面附加一个\ n,然后将两个字符串写入光盘。

驱动程序已扩展,可以使用以下新的访问者:


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();  
} 
如您所见,所需要做的就是创建AutomobileArchive对象并随其访问Vehicle *。 接受AutomobileVisitor后,可以使用Serialize方法()将Automobile保存到光盘。 在要保存许多车辆对象的地方,必须先检查每个对象,然后将它们序列化到光盘上。 上面的代码将需要针对各种车辆进行复制。

本示例仅出于概念目的。 不同类型车辆的序列化细节已被省略。

使用手柄

由于指针语法是众所周知的,因此本文中的示例使用指针。 但是,建议在实际使用的应用程序中使用。 您应该参考“ C / C ++文章”部分中有关“句柄”的文章。

更多的信息

请参阅Erich Fromm等人的《设计模式》一书,Addison-Wesley,1994年。

本文仅显示“访客”模式的概念基础,而不显示使用此模式的动机和后果。

版权所有2007 Buchmiller Technical Associates美国北华盛顿本德

From: https://bytes.com/topic/c/insights/674645-design-patterns-visitor

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值