zar
zar

Reputation: 12227

Composite pattern with variations in classes

I have a situation where class A, B, and C, all are derived from X to make a composite pattern.

Now A, B, and C are very similar but they do have different internal attributes. My question is how do I access their attributes and modify them once they are added to composite without typecasting it?

In my real example, the part-whole relationship perfectly makes sense and all elements support the main operation which is why I need it. That said individual classes in the composite does have different attributes and requires special operations.

Is there a way to decouple these special properties and functions related to them away from composite class but attach to them?

To put things in perspective, let's say we have composite merchandise (base class of composite) which has a price. Now a store is a merchandise, in it, a department is a merchandise, in which an actual item say a potis merchandise, it can have a pot-set with combination pots which is also merchandise and so on.

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;
}

The problem is with this line once I have filled up the composite.

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

First, from my code structure, I know this should be a certain type but as a best practice, we are not supposed to typecast this!? But I do want to change its properties which are unique to it so how do I achieve that?

Assume this store sells shirts as well and I want to change its properties (or even just to display them!), it is very different from a pot.

What would be the best approach here? I think if we can somehow decouple the unique properties of each composite into different classes, this way composite will stay leaner too but not sure how to achieve that.

I assume, in real life, there is no perfect composite and the constituent classes will have some differences. How do we handle that?

Update

Please note I have used the merchandise example to explain the problem. In my real example, A, B, C are all derived from X. A contains multiple B which contains multiple C items. When an operation is performed on A, it needs to be performed on its constituents and that's why I am using composite. But then, each composite does have different attributes. Is composite not a good fit for this?

Upvotes: 3

Views: 1280

Answers (4)

Useless
Useless

Reputation: 67733

Concerning Your Design Choices

Let's compare the GoF book description of the intent of the Composite pattern with your requirements:

  • Compose objects into tree structures that represent whole-part hierarchies.

    In your example a shirt is not part of a shop, and a shop is not really an item of merchandise.

    You say this does make sense in your actual code, but I can only comment on the code you actually showed.

  • Composite lets clients treat individual objects and compositions of objects uniformly.

    In your comment you say you don't really want to treat each type of object uniformly.

So, it's at least not obvious that Composite is a good fit, because the description doesn't match your use case (at best, it half-fits your described use case but not your sample code).

For comparison, the motivating example from that book is a drawing app, where treating the main canvas as a Drawable object containing other Drawable sub-objects (of different types like lines, polygons and text) is useful for rendering. In that case each object is a drawable part of the drawable whole, and it focuses on the case when you do want to treat them uniformly (ie, issuing a single draw call to the top-level canvas).

A match has an innings (has score/result), both side which are playing has their inning (has scores), then each player who is playing has his innings (score with more details). When match progresses, an event is added to the match which than is added to current innings and than current player innings. So I build up score this way but at the end I want to display it and each innings type is rather different and setting up current state requires different operations.

OK, an innings is part of a match, but do you have any more levels? Does an innings consist of balls or other events, or is it just ... an innings number, a player and a score?

If you can easily model a game as a list of innings, and link each innings to a player for per-player reports, that seems a lot simpler.

Even if you have another level, if you only want to deal with objects heterogeneously (according to their different types, instead of homogeneously as if they're all the same), you can just write that structure explicitly.


Concerning Your Composite Implementation

The getMerchandise interface is poor. Why does it return only the first of potentially many objects? Why do you need to get them anyway?

You mention two use cases:

  1. changing an object's properties

    Presumably you know which object you want to alter, right? Say you want to change the price of the object loosely identified as Mens>Shirts>Wrangler. Either

    • ask the store to forward a Visitor to that object by name (so the store finds the department called "Mens" and asks that to forward the Visitor to a child matching Shirts>Wrangler).

    • just find the Shirt("Wrangler") object directly in some other index (eg. by stock number) and deal with it directly. You don't have to do everything via the Composite pattern even if you do use it.

  2. displaying an object

    But the whole point of the Composite pattern is that every object should implement a virtual display (or whatever) method. You use this when you want to let every type know how to display itself.

Upvotes: 1

JaMiT
JaMiT

Reputation: 16853

Merchandise: a commodity offered for sale

Now a store is a merchandise,

This is true only if you are selling the store. Otherwise it is better described as a container of merchandise, not a composite.

Even if you are in the business of selling stores, it might (depending on the context of the program) be prudent to treat it as a different kind of merchandise (different base class) since the selling environment is rather different.

in it, a department is a merchandise,

Departments within a store are rarely sold to other stores, so I highly doubt this claim. Again, you have something that contains merchandise, not something composed of merchandise.

in which an actual item say a pot is merchandise, it can have a pot-set with combination pots which is also merchandise and so on.

Yes, this much is good. Pots are offered for sale. A pot-set combination sounds like a good example of composite merchandise, as the set is offered for sale, and its components might be packaged and offered for sale separately, perhaps even on the same shelf.

Speaking of the shelf, that would be another example of a container of merchandise.


It looks like you might be after containers rather than composites. A store could have a vector of departments (or perhaps a set if you want to find them by name). The department in turn could have a container of merchandise within that department. Or perhaps the department would contain aisles, which then contain shelves, which then contain merchandise.

If you need the inventory of the entire store, there are a few options. One would be to have the store iterate over its departments, which then iterate over their inventories. Ideally, you would implement this within the store class so that code outside the class does not need to know about this structure. Another option would be for the store to maintain its own container of merchandise. This would mean that a single item of merchandise would logically be in multiple containers (e.g. a store's and a department's). This suggests using containers of shared_ptr, so that each item of merchandise is still represented by a single data object.

Regardless of the implementation chosen, the relationship between "store" and "merchandise" is better described as "has" rather than "is".

Upvotes: 1

Oliv
Oliv

Reputation: 18051

I think you are looking for the visitor design pattern, it keeps clean interfaces and makes code much more flexible.

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;
   }

Upvotes: 3

Lesley Lai
Lesley Lai

Reputation: 302

It seems like what you want to do is gathering RTTI (Run-time type information), so dynamic_cast is the solution. Also, if you are using C++17, I will recommend you use std::variant (or boost::variant if you are using lower version of C++ but are using boost.) If you do not want to use them, then maybe you can make your add a template function and returns a reference to the underlying type.

By the way

  • in your main there are a bunch of dynamic allocations that never get deallocated. Use smart pointers if you have C++ with version at least C++11.
  • your base classes do not have virtual destructors, this will cause huge problems when destroying your store
  • Don't use using namespace std
  • If you have C++11, use override keyword when you want to override a virtual function
  • You should mark show() const.

Upvotes: 2

Related Questions