行为型模式--访问者

1、意图

  表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

2、结构

  

 

 3、参与者

  Visitor:访问者。为该对象结构中ConcreteElement的每一个类声明一个Visit操作。该操作的名字和特征标识了发送Visit请求给该访问者的那个类。这使得访问者可以确定正被访问元素的具体的类。这样访问者就可以通过该元素的特定接口直接访问它。

  ConcreteVisitor:具体访问者。实现每个由Visitor声明的操作。每个操作实现本算法的一部分,而该算法片断乃是对应于结构中对象的类。ConcreteVisitor为该算法提供了上下文并存储它的局部状态。这一状态常常在遍历该结构的过程中累积结果。

  Element:元素。定义一个Accept操作,它以一个访问者为参数。

  ConcreteElement:具体元素。实现Accept操作,该操作以一个访问者为参数。

  ObjectStructure:对象结构。能枚举它的元素;可以提供一个高层的接口以允许该访问者访问它的元素;可以是一个复合(Composite)或是一个集合,如一个列表或一个无序集合。

4、适用性

  在以下情况使用访问者模式:

  1)一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。

  2)需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。访问者模式(Visitor)使得你可以将相关的操作集中起来定义在一个类中。当该对象结构被很多应用共享时,用Visitor模式让每个应用仅包含需要用到的操作。

  3)定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价。如果对象结构类经常改变,那么可能还是在这些类中定义这些操作较好。

5、代码示例

class Equipment
{
public:
    virtual ~Equipment ();
    const char* Name(){return _name;}
    virtual Watt Power(); 
    virtual Currency Netprice();
    virtual Currency DiscountPrice(); 
    virtual void Accept(Equipmentvisitor&); 
protected:
    Equipment(const char*); 
private:
    const char* _name;
};

// 各Equipment操作返回设备的属性,例如它的功耗和价格。对于特定种类的设备(如底盘、发动机和平面板),子类适当地重定义这些操作。
// 如下所示,所有设备访问者的抽象父类对每一个设备子类都有个虚函数。所有的虚函数的缺省行为都是什么也不做。
class Equipmentvisitor 
{
public:
    virtual ~Equipmentvisitor(); 
    virtual void visitFloppyDisk(FloppyDisk*); 
    virtual void visitcard(Card*); 
    virtual void visitChassis(Chassis*); 
    virtual void visitBus(Bus*);
    
    // and so on for other concrete subclasses of Equipment 
protected:
    Equipmentvisitor();
};
// Equipment子类以基本相同的方式定义Accept,调用EquipmentVisitor中的对应于接受Accept请求的类的操作,如:
void FloppyDisk::Accept (Equipmentvisitor& visitor)
{
    visitor.VisitFloppyDisk(this);
}

// 包含其他设备的设备(尤其是在Composite模式中CompositeEquipment的子类)实现Accept时,遍历其各个子构件并调用它们各自的Accept操作
// 然后对自己调用Visit操作。例如Chassis::Accept可像如下这样遍历底盘中的所有部件:
void Chassis::Accept (Equipmentvisitor& visitor)
{
    for(ListIterator<Equipment*> i(_parts); !i.IsDone(); i.Next()
    {
        i.CurrentItem()->Accept(visitor); 
    }
    visitor.visitchassis(this);
}
// EquipmentVisitor的子类在设备结构上定义了特定的算法。PricingVisitori计算该设备结构的价格。
// 它计算所有的简单设备(如软盘)的实价以及所有复合设备(如底盘和公共汽车)打折后的价格。
class PricingVisitor : public Equipmentvisitor
{
public:
    Pricingvisitor(); 
    Currency& GetTotalPrice(); 
    virtual void visitFloppyDisk(FloppyDisk*); 
    virtual void visitcard(Card*);
    virtual void VisitChassis(Chassis*); 
    virtual void visitBus(Bus*);
    // ...
private:
    Currency _total;
};

void Pricingvisitor::visitFloppyDisk (FloppyDisk* e)
{
    _total += e->NetPrice();
}

void Pricingvisitor::Visitchassis (Chassis* e)
{
    _total += e->Discountprice();
}

// PricingVisitor将计算设备结构中所有结点的总价格。注意PricingVisitor在相应的成员函数中
// 为一类设备选择合适的定价策略。此外,我们只需改变PricingVisitor类即可改变一个设备结构的定价策略。
// 我们可以像这样定义一个计算存货清单的类:
class Inventoryvisitor : public Equipmentvisitor
{
public:
    Inventoryvisitor(); 
    Inventory& GetInventory ();
    virtual void visitFloppyDisk(FloppyDisk*); 
    virtual void visitCard(Card*); 
    virtual void visitchassis(Chassis*); 
    virtual void visitBus(Bus*);
    // ...
private:
    Inventory _inventory;
};

// InventoryVisitor为对象结构中的每一种类型的设备累计总和。InventoryVisitor使用一个Inventory类,
// Inventory类定义了一个接口用于增加设备(此处略去)。
void Inventoryvisitor::VisitFloppyDisk (FloppyDisk* e)
{
    _inventory.Accumulate(e); 
}
void Inventoryvisitor::VisitChassis (Chassis* e)
{
    _inventory.Accumulate(e);
}
// 下面是如何在一个设备结构上使用InventoryVisitor
Equipment* component;
Inventoryvisitor visitor; 

component->Accept(visitor); 
cout << "Inventory" << component->Name()<< visitor.GetInventory();

6、总结

  访问者模式将对象上的数据结构与作用在这些数据结构上的操作分离开,实现数据与操作的解耦。

  访问者模式下,操作集合可以自由演化,不同的操作对应不同的访问者。当需要新增某类操作时,只需新增访问者即可,而不需改动具体访问的元素。

  访问者模式针对数据结构(元素)多且固定的对象。访问者模式不适用于那些数据结构经常变化的对象。

  访问者模式中,对象中的每个待访问的元素,需定义Accept接口,接收访问者的访问(函数入参为访问者的指针或引用)。而每个访问者,需要定义访问该元素的接口,并实现访问的具体行为,供元素的Accept接口调用。

posted @ 2022-05-04 20:29  流翎  阅读(34)  评论(0编辑  收藏  举报