与访问者的复合模式,访问者真正进入了什么以及如何?

Composite pattern with visitor, what really goes in visitor and how?

复合模式经常与访问者一起使用。我试图弄清楚到底什么进入了复合材料,什么进入了访问者。例如,如果其中一个组合具有唯一性 property/attribute,它是否仍会存储在其中并且访问者只会将其挖掘出来或访问者将保留它?

我已经写了一个快速演示来说明这个问题(注意:我已经最小化了代码)。

using namespace std;

class Visitor
{
public:
    virtual string visit(class Manager * manager) = 0;
    virtual string visit(class SalesPerson * salesPerson) = 0;

};


class Employee
{
public:
    virtual void add(Employee * employee) = 0;
    virtual void remove(Employee * employee) = 0;
    virtual string name() = 0;
    virtual string department() = 0;
    virtual int salary() = 0;

    virtual void awardBonus(int amount) = 0;

    virtual void accept(Visitor * v) = 0;

protected:
    string m_name;
    string m_dept;
    int m_salary;

};



class Manager : public Employee
{
protected:
    QList <Employee *> subordinates;

    virtual void add(Employee * employee)
    {
        subordinates.append( employee );
    }
    virtual void remove(Employee * employee) {};
    virtual string name() { return m_name; };
    virtual string department() { return m_dept; };
    virtual int salary() { return m_salary; };

    virtual void awardBonus(int amount) {};

    void accept(Visitor *v)
    {
        v->visit(this);
    }

};

class SalesPerson: public Employee
{
public:
    float commision; // sales employee gets commision
    string territory;

    SalesPerson(): territory("Unknown") {};

    void accept(Visitor *v)
    {
        v->visit(this);
    }

    virtual void add(Employee * employee) {};
    virtual void remove(Employee * employee) {};
    virtual string name() { return m_name; };
    virtual string department() { return m_dept; };
    virtual int salary() { return m_salary; };

    virtual void awardBonus(int amount) {};

};



class AwardStockOptionsVisitor : public Visitor
{
public:
    int shares;

    string visit(Manager *manager)
    {
        shares = 200;

    }

    string visit(SalesPerson *salesPerson)
    {
        shares = 100;
    }

};

class GetTerritoryVisitor : public Visitor
{
public:
    string territory;
    string visit(Manager *manager)
    {
        return "";
    }

    string visit(SalesPerson *salesPerson)
    {
        territory = salesPerson->territory;
        return salesPerson->territory;
    }

};


int main(int argc, char *argv[])
{
    Employee * manager = new Manager;

    Employee * salesPerson = new SalesPerson;

    GetTerritoryVisitor * getTerritory = new GetTerritoryVisitor;
    salesPerson->accept( getTerritory );

    cout << "Sales territory is " << getTerritory->territory  << endl;

    manager->add( salesPerson );
}

在此示例中,一名员工可以拥有很多属性,其中一些属性因员工类型而异。

例如 SalesPerson 有一个 territory 属性,它不适用于其他员工,组合是否仍然存储它,而 visitor 只检索它的值?在那种情况下,如果我需要这些操作,我们是否需要 setValue()getValue() 的两个访问者?第二个范例是访问者是否应该存储这个属性,本质上是添加这个 属性,否则它不会存在于 employee?

假设股票期权 属性 只提供给 5% 的员工。这应该存储在复合中并由访问者扩展吗?

我担心的一个问题是员工组合可能有许多额外的属性,这也可能意味着派生 class 中的差异会扩大。如果 composite 具有相对较大的属性但实际上并不适用于所有子 classes,这可以吗?我们应该通过访问者添加 属性 还是仅使用访问者来为唯一子 class 可能具有的特定附加属性提供接口?

虽然这两种设计模式是互补的,但它们的意图是不同的:

  • composite是一种结构模式。目的是表示层次结构并统一处理单个对象和对象的组合。

  • visitor是一种行为模式。目的是表示要对 objective 结构的元素执行的操作。

根据这个原则,访问者不是用来存储持久数据的。所以,是的,可能特定于某类员工的任何属性都应保留在组合中。

您拥有大量属性集(无论是特定于一名员工派生的还是所有员工共有的)这一事实不会改变原则。访问者模式旨在处理组合中不同类型的元素,使用不同的访问成员函数(每个不同的 class 一个)。

唯一的不便之处是,当您向复合结构中添加新的 class 时,您需要相应地修改访问者。

您可以暂时将一些值放入访问者中以执行操作。例如:

  • 你可以有一个访问者来汇总经理以下的人数,以及他们的总薪水
  • 或者您可以拥有访问者的股份总数,这会实施一种算法,以分层方式将这些股份分配给员工。在分配结束时,访客中剩余的股份将为 0,因此您需要在组合中有一些股东计数器。

实现问题:

您的访问者模式实现不完整。您没有通过复合结构的访问逻辑。

看看您打算使用访问者的方式,我想知道您是否不希望将 attributes/functions 添加到对象,而不是浏览它们。

我建议你看一下 decorator pattern,它更适合这种用法:它是一种结构模式,扩展了现有的 classes 系列。

问题是从 Visitorvisit 方法返回一些东西不会真正起作用:

class GetTerritoryVisitor : public Visitor
{
public:
    string territory;
    string visit(Manager *manager)
    {
        return ""; // who will use this?
    }

    string visit(SalesPerson *salesPerson)
    {
        territory = salesPerson->territory; // this will be overwritten
        return salesPerson->territory; // and who will use this?
    }

};

Visitor + Composite 的工作方式是这样的(伪代码):

class Composite {

     function accept(Visitor visitor) {
         foreach (element in this->getElements()) {
             element->accept(visitor);
         }
         this->accept(visitor);
     }
 }

 Visitor visitor = new GetTerritoryVisitor();
 composite->accept(visitor);

请记住,在一般情况下,visitor 对象会访问许多对象。因此无法 "capture" 从其 visit 方法返回的值。

此外,如果您将单个结果保存在 属性 中,就像您在 GetTerritoryVisitor 中所做的那样,它也不会起作用,因为它会被每个 SalesPerson 覆盖访问。

通常Visitor对对象做一些事情或积累一些信息。

所以我可以想象创建销售人员区域列表的访问者:

class GetTerritoryVisitor : public Visitor {
    list territories;

    string visit(SalesPerson *salesPerson) {
        this->territories->add(salesPerson->territory);
    }

    list getTerritories() {
        return this->territories;
    }

};

Visitor visitor = new GetTerritoryVisitor();
composite->accept(visitor);
// now we have a list of territories of all sales mans
// and can use it for something
allTerritories = visitor->getTerritories();