类变化的复合模式

时间:2018-08-10 22:04:33

标签: c++ oop design-patterns composite

我遇到的情况是,类A,B和C都是从X派生出来的,以形成复合模式。

现在,A,B和C非常相似,但是它们具有不同的内部属性。我的问题是,一旦将它们添加到复合材料中,而又不进行类型转换,如何访问它们的属性并修改它们?

在我的实际示例中,部分-整体关系完全合理,并且所有元素都支持主要操作,这就是我需要它的原因。也就是说,组合中的各个类确实具有不同的属性,并且需要特殊的操作。

是否有一种方法可以将与它们相关的这些特殊属性和功能与复合类分离开来,而是附加到它们上?

为了使事情更直观,假设我们有一个复合merchandise(复合的基类),它具有价格。现在,商店是一种商品,部门是一种商品,其中实际的商品说pot是商品,它可以有一个带有组合罐的锅具,该罐也是商品,依此类推。 / p>

class Merchandise
{
public:
    virtual void add(Merchandise* item) = 0;
    virtual Merchandise* getMerchandise() = 0;
    virtual void show() = 0;

    // assume we have the following key operations here but I am not implementing them to keep this eitemample short
    //virtual setPrice(float price) = 0;
    //virtual float getPrice() = 0;
};


class Store : public Merchandise
{
    vector< Merchandise*> deparments;
    std::string storeName = "";

public:
    Store(std::string store_name) : storeName(store_name) {}

    virtual void add(Merchandise* item)
    {
        deparments.push_back(item);
    }

    virtual Merchandise* getMerchandise()
    {
        if (deparments.size() > 0)
            return deparments[0];

        return 0;
    }

    virtual void show()
    {
        cout << "I am Store " << storeName << " and have following " << deparments.size() << " departments" << endl;

        for (unsigned int i = 0; i < deparments.size(); i++)
        {
            deparments[i]->show();
        }
    }
};

class Department : public Merchandise
{
    std::string depName;
    vector<Merchandise*> items;
public:
    Department(std::string dep_name) : depName(dep_name) {}
    virtual void add(Merchandise* item)
    {
        items.push_back(item);
    }

    virtual Merchandise* getMerchandise()
    {
        if (items.size() > 0)
            return items[0];

        return 0;
    }

    virtual void show()
    {
        cout << "I am department " << depName << " and have following " << items.size() << " items" << endl;

        for (unsigned int i = 0; i < items.size(); i++)
        {
            items[i]->show();
        }
    }
};

class Shirt : public Merchandise
{
    std::string shirtName;
public:
    Shirt(std::string shirt_name) : shirtName(shirt_name) {}
    virtual void add(Merchandise* item) {}
    virtual Merchandise* getMerchandise() { return 0; }
    virtual void show()
    {
        cout << "I am shirt " << shirtName << endl;
    };

};

class Pot : public Merchandise
{
    std::string potName;
public:
    Pot(std::string pot_name) : potName(pot_name) {}

    virtual void add(Merchandise* item) {  }
    virtual Merchandise* getMerchandise() { return 0; }
    virtual void show()
    {
        cout << "I am pot " << potName << endl;
    };

    int num = 0;
};

class CookSet : public Merchandise
{
    std::string cooksetName;
    vector<Merchandise*> pots;
public:
    CookSet(std::string cookset_name) : cooksetName(cookset_name) {}
    vector<Merchandise*> listOfPots;
    virtual void add(Merchandise* item) { pots.push_back(item); }
    virtual Merchandise* getMerchandise() { return 0; }
    virtual void show()
    {
        cout << "I am cookset " << cooksetName << " and have following " << pots.size() << " items" << endl;

        for (unsigned int i = 0; i < pots.size(); i++)
        {
            pots[i]->show();
        }
    };

    int num = 0;
};

int main()
{
    // create a store
    Store * store = new Store( "BigMart");

    // create home department and its items
    Department * mens = new Department( "Mens");

    mens->add(new Shirt("Columbia") );
    mens->add(new Shirt("Wrangler") );

    // likewise a new composite can be dress class which is made of a shirt and pants.

    Department * kitchen = new Department("Kitchen");

    // create kitchen department and its items
    kitchen->add(new Pot("Avalon"));

    CookSet * cookset = new CookSet("Faberware");
    cookset->add(new Pot("Small Pot"));
    cookset->add(new Pot("Big pot"));

    kitchen->add(cookset);

    store->add( mens );
    store->add(kitchen);

    store->show();

    // so far so good but the real fun begins after this.

    // Firt question is, how do we even access the deep down composite objects in the tree?

    // this wil not really make sense!
    Merchandise* item = store->getMerchandise()->getMerchandise(); 

    // Which leads me to want to add specific method to CStore object like the following to retrieve each department 
    // but then does this break composite pattern? If not, how can I accomodate these methods only to CStore class?

    //store->getMensDept();
    //store->getsKitchenDept();

    // Likewise a shirt class will store different attributes of a shirt like collar size, arm length etc, color.
    // how to retrieve that?

    // Other operations is, say if item is cookset, set it on 20% sale.

    // Another if its a shirt and color is orange, set it on 25% sale (a shirt has a color property but pot doesn't).

    // how to even dispaly particular attributes of that item in a structure?
    // item->getAttributes();


    return 0;
}

问题出在我填写完复合材料之后。

Merchandise* item = store->getMerchandise()->getMerchandise();

首先,从我的代码结构中,我知道这应该是某种类型,但是作为最佳实践,我们不应该对此类型进行类型转换!但是我确实想更改其特有的属性,那么如何实现呢?

假设这家商店也出售衬衫,并且我想更改其属性(甚至只是为了展示它们!),它与锅子有很大的不同。

这里最好的方法是什么?我认为,如果我们能够以某种方式将每个组合的独特属性分离为不同的类,则这种方法也将保持精简,但不确定如何实现。

我认为,在现实生活中,没有完美的组合,构成类将会存在一些差异。我们该如何处理?

更新

请注意,我已经使用merchandise示例来解释该问题。在我的真实示例中,ABC都是从X派生的。 A包含多个B,其中包含多个C项目。在A上执行操作时,需要对其组成部分执行操作,这就是为什么我要使用Composite。但是,每个复合材料确实具有不同的属性。复合材料不是很适合吗?

4 个答案:

答案 0 :(得分:3)

我认为您正在寻找visitor design pattern,它可以保持简洁的界面并使代码更加灵活。

class Shirt;
class Pot;

class visitor{
public:
//To do for each component types:
virtual void visit(Shirt&) =0;
virtual void visit(Pot&) =0;
};

class Merchandise
{
public:
    //...

    //Provides a default implementation for the acceptor that
    //that do nothing.
    virtual void accept(visitor& x){}

    // assume we have the following key operations here but I am not implementing them to keep this eitemample short
    //virtual setPrice(float price) = 0;
    //virtual float getPrice() = 0;
    //  => implementable with a visitor
};


class Store : public Merchandise
{
    //...

    void accept(visitor& v) override
    {

        for (unsigned int i = 0; i < deparments.size(); i++)
        {
            //forward the visitor to each component of the store.
            //do the same for departments
            deparments[i]->accept(v);
        }
    }
};

class Shirt : public Merchandise
{
//...
void accept(visitor& v) override{
     //now *this as the type Shirt&, pass this to the visitor.
     v.visit(*this);
     }

};

class Pot : public Merchandise
{
//...
void accept(visitor& v) override{
   v.visit(*this);
   }
};

class SpecialPromotion
  :public visitor{
     public:
     void visit(Shirt& s) override {
        //25% discount on orange shirts, 15% otherwise;
        if (s.color="orange")
          s.price*=0.75;
        else
          s.price*=0.85
        }
     void visit(Pot& p) override{
        //one pot offered for each pot pack
        ++p.num;
        }
     };

//example of usage:
void apply_special_promotion(Store& s){
   SpecialPromotion x;
   s.accept(x);
   }

 class EstimateStockMass
  :public visitor{
     public:
     double mass=0;
     void visit(Shirt& s) override {
        if (s.size="XL") mass+=0.5;
        else mass+=0.1;
        }
     void visit(Pot& p) override{
        mass += p.num * 1.2;
        }
     };
     //example of usage:
double get_stock_mass(Store& s){
   EstimateStockMass x;
   s.accept(x);
   return x.mass;
   }

答案 1 :(得分:2)

似乎您想做的是收集RTTI(运行时类型信息),因此dynamic_cast是解决方案。另外,如果您使用的是C ++ 17,我建议您使用std::variant(如果您使用的是较低版本的C ++,但您使用的是boost,则建议使用boost::variant。)要使用它们,那么也许可以将add用作模板函数并返回对基础类型的引用。

顺便

  • 您的主要系统中有一堆永不释放的动态分配。如果您的C ++版本至少为C ++ 11,请使用智能指针。
  • 您的基类没有虚拟析构函数,这将在破坏您的商店时引起巨大的问题
  • Don't use using namespace std
  • 如果您具有C ++ 11,则要覆盖虚拟函数时,请使用override关键字
  • 您应标记show() const

答案 2 :(得分:1)

关于您的设计选择

让我们将复合模式意图的GoF书籍描述与您的要求进行比较:

  •   

    将对象组成代表整个层次结构的树结构。

    在您的示例中,衬衫不是商店的一部分,商店实际上不是商品。

    您说这确实在您的实际代码中是有道理的,但我只能对您实际显示的代码发表评论。

  •   

    Composite使客户可以统一对待单个对象和对象组成。

    在您的评论中,您说您真的不想统一对待每种类型的对象。

因此,显而易见至少不是一个合适的组合,因为该描述与您的用例不匹配(充其量只能与您描述的用例一半相配,但与您的样本不符)代码)。

为了进行比较,该书中的一个激励性示例是一个绘图应用程序,该程序将主画布视为包含其他Drawable子对象(线,多边形和文本等不同类型的子对象)的Drawable对象,对于渲染很有用。在那种情况下,每个对象 是可绘制整体的一个可绘制部分,并且着重于当您 do 想要统一对待它们的情况(即,发出单个绘制调用)到顶层画布。

  

一场比赛有一个局(有得分/结果),正在比赛的双方都有局(有得分),然后每个正在玩的球员都有他的局(得分更多的细节)。当比赛进行时,会将事件添加到比赛中,然后将事件添加到当前局和当前玩家局中。所以我以这种方式建立分数,但是最后我要显示分数,每种局类型都不同,设置当前状态需要不同的操作。

好的,一局是比赛的一部分,但是您还有其他关卡吗?一局是由球或其他事件组成,还是……一局数字,一个玩家和一个比分?

如果您可以轻松地将游戏建模为局列表,并将每个局链接到玩家以获取每个玩家的报告,那么这似乎要简单得多。

即使您拥有另一个级别,如果您想要异构处理对象(根据它们的不同类型,而不是像它们一样一样地均匀处理),则只需编写该结构。


关于您的复合实现

getMerchandise界面较差。为什么它只返回潜在多个对象中的第一个?为什么仍然需要获得它们?

您提到了两个用例:

  1. 更改对象的属性

    大概您知道要更改哪个对象,对吗?假设您要更改松散标识为Mens>Shirts>Wrangler的对象的价格。要么

    • 要求商店将访问者按名称转发给该对象(因此商店找到名为"Mens"的部门,并要求那个将访问者转发给与{ {1}}。

    • 只需直接在其他索引中找到Shirts>Wrangler对象(例如,按库存编号)并直接处理即可。即使您使用复合模式,也不必通过复合模式进行所有操作。

  2. 显示对象

    但是Composite模式的重点是每个对象都应实现虚拟的Shirt("Wrangler")(或其他任何方法)。当您想让每种类型都知道如何显示自己时,可以使用它。

答案 3 :(得分:1)

Merchandise:要出售的商品

  

现在商店是商品

仅当您出售商店时才如此。否则,最好将其描述为商品的“容器” ,而不是组合物。

即使您从事销售商店业务,由于销售环境完全不同,(根据程序的上下文)将其视为另一种商品(不同的基本类别)也是谨慎的。 / p>

  

其中部门是商品,

商店中的部门很少出售给其他商店,因此我对此说法表示高度怀疑。同样,您拥有包含商品的东西,而不是包含商品的东西。

  

其中一个实际物品说一个锅是商品,它可以有一个锅具组合锅,它也是商品,依此类推。

是的,这很好。锅出售。锅具组合听起来像是复合商品的一个很好的例子,因为它是要出售的,它的组件可能被包装并单独出售,甚至在同一架子上也可以出售。

说到货架,那将是商品容器的另一个例子。


看起来您可能是在追求容器而不是复合材料。一家商店可以有vector个部门(如果要按名称查找,则可以有set个部门)。反过来,部门可以在该部门内放置一个商品容器。或者,部门可能包含过道,然后包含货架,然后包含商品。

如果您需要整个商店的库存,则有一些选择。一种是让商店遍历其部门,然后遍历其库存。理想情况下,您可以在store类中实现此功能,以便类外的代码无需了解此结构。商店的另一种选择是维持自己的商品容器。这意味着单件商品在逻辑上将放在多个容器中(例如商店和部门的容器)。这建议使用shared_ptr的容器,这样,商品的每个项仍由单个数据对象表示。

无论选择哪种实现方式,“商店”和“商品”之间的关系都可以更好地描述为“有”而不是“是”。

相关问题