Search code examples
c++qtdesign-patternscomposite

Is it okay if each composite item in composite pattern has lots of unique properties of its own?


Typical composite pattern books shows leaner examples (obviously) where all composite items are somewhat relatable and often use one common method name for illustration like name() in folder and files example.

I have deployed composite pattern in project where it makes good sense as in 'whole-part relationship' in a tree like structure. However they really have only two common attributes (=methods), rest of their attributes or properties of each node are quite different although relatable.

So I ended up with a lot of specific functions for each derived type in the base CompositeItem class and by that I mean like 10 to 15 methods which I am illustrating with the following example.

class CompositeItem
    {
        CompositeItem();

        // common methods, applies to all derived classes
        virtual void hello();
        virtual void foo();

        // these apply only to derived1 class
        virtual void method1();
        virtual void method2();
        virtual void method3();
        virtual void method4();
        virtual void method5();
        virtual void method6();
        virtual void method7();
        virtual void method8();
        virtual void method9();
        virtual void method10();

        // these applies to only derived2 class
        virtual void method11();
        virtual void method12();
        virtual void method13();
        virtual void method14();
        virtual void method15();
        virtual void method17();
        virtual void method17();
        virtual void method18();
        virtual void method19();
        virtual void method20();

        // these applies to only derived3 class
        virtual void method21();
        virtual void method22();
        virtual void method23();
        virtual void method24();
        virtual void method25();
        virtual void method26();
        virtual void method27();
        virtual void method28();
        virtual void method29();
        virtual void method30(;)

        // these applies to only derived4 class
        virtual void method31();
        virtual void method32();
        virtual void method33();
        virtual void method34();
        virtual void method35();
        virtual void method36();
        virtual void method37()
        virtual void method38();
        virtual void method39();
        virtual void method40();
    }

Since I have not really seen composite in action on this scale so I am looking for validation, does this sound normal or there any red flags?

I know I haven't included too specific information about project but given all items share only 2 methods but each have 10 to 15 different methods unique to that derived item only , does this sound okay and in general compliance of the pattern?

Update 1

One of the reason I have these so amy functions is because I need these to retrieve different attributes or properties of each derived class type. One thing I have notice is that it is easy to stuff things into composite but not so much when you need to retrieve specific information about each time and show it in a view - that's what I am doing.

My application has left view with all these things in tree. I do need to show specific/unique information about each node (=subclass) in the right pane of my view. if composite is not good fit, is there any other pattern that fits?

Update 2

I can summarize my dilemma with composite the following way (note code written on fly, please forgive typos):

class CompositeItem 
{ 
    virtual void add(CompositeItem *item);
};

class Species : public CompositeItem 
{
    QList< CompositeItem *> species;
}

class Mammals: public CompositeItem 
{
    QList<CompositeItem *> mammals;
    // assume I have 10 methods here for various attributes about mammals

}

class Tiger: public CompositeItem
{
    // assume I have 10 methods to various specific attributes about tigers

}

CompositeItem * species = new Species;

CompositeItem * mammals = new Mammals( lives_on_land, has_4_legs, etc);

CompositeItem * tiger = new Tiger(eats_meat, hunts_alone, etc);

mammals->add(tiger);
species.add(mammals);

Now as you can see when I create a specie or group, I have access to it directly and I can set all its attributes.

The problem is once I add that item to Composite container and sing off, now when when I need to deal with this item later, I have to deal with it as CompositeItem * pointer. That means either I have to type cast (I assume bad) or use virtual methods specific to that item to base class to access it. This is my problem! Too many virtual functions!

I wanted to add the above to give a better picture and show the exact nature of my problem and if there could be a better solution for this scenario. Thanks!


Solution

  • The composite pattern is made to treat objects and collection of objects uniformly, it is exactely the case when you want to display a hierarchy of objects without taking care of the concrete collection (tree, list, graph, ...) and without taking care of the content (concrete classes behind your CompositeItem interface).

    Here it seems that your CompositeItem interface is only a base class to many derived classes that have a little in common and the concept of collection is not really present. In that sense, the composite pattern does not fit very well ; However, at this point it impossible to say whether one of your derived class is a collection or not.

    Now you are asking if it is normal to get an interface of several virtual methods with a few concerning every classes and a lot of concerning only one derived class ?

    No, it is a code smell. You should remove the "specific methods" from your interface because they don't make sense and pollute your interface.

    But your purpose is still to manipulate many of those objects by the compositeItem interface but with the help of many specific methods ? Right ?

    The problem is that all of your specific stuff can't be regrouped under new interface methods and you don't want to rely on dynamic_cast and other RTTI methods to achieve your goal.

    So how to do that ?

    You have to decouple the object hierarchy of the algorithm that you want to apply to. The Composite/Visitor couple completely serves this purpose.

    The visitor pattern will crawl your TreeView uniformly, going from item to item, allowing you to use either the CompositeItem interface methods, either the specific stuff on these items.

    The only drawback is that you'll have to distribute some of the derived class logic to the visitor(s) class(es)

    Edit : https://sourcemaking.com/design_patterns/visitor