previous up next Composite

Intent, Motivation, Applicability, Structure, Participants, Collaborations, Consequences, Implementation, Sample Code, Known Uses, Related Patterns

previous next Implementation

previous up next There are many issues to consider when implementing the Composite pattern:

  1. Declaring the child management operations. Although the Composite class implements the Add and Remove operations for managing children, an important issue in the Composite pattern is which classes declare these operations in the Composite class hierarchy. Should we declare these operations in the Component and make them meaningful for Leaf classes, or should we declare and define them only in Composite and its subclasses?

    The decision involves a trade-off between safety and transparency:

    We have emphasized transparency over safety in this pattern. If you opt for safety, then at times you may lose type information and have to convert a component into a composite. How can you do this without resorting to a type-unsafe cast?

    One approach is to declare an operation Composite* GetComposite() in the Component class. Component provides a default operation that returns a null pointer. The Composite class redefines this operation to return itself through the this pointer:

    class Composite; class Component { public: //... virtual Composite* GetComposite() { return 0; } }; class Composite : public Component { public: void Add(Component*); // ... virtual Composite* GetComposite() { return this; } }; class Leaf : public Component { // ... };
    GetComposite lets you query a component to see if it's a composite. You can perform Add and Remove safely on the composite it returns.
    Composite* aComposite = new Composite; Leaf* aLeaf = new Leaf; Component* aComponent; Composite* test; aComponent = aComposite; if (test = aComponent->GetComposite()) { test->Add(new Leaf); } aComponent = aLeaf; if (test = aComponent->GetComposite()) { test->Add(new Leaf); // will not add leaf }
    Similar tests for a Composite can be done using the C++ dynamic_cast construct.

    Of course, the problem here is that we don't treat all components uniformly. We have to revert to testing for different types before taking the appropriate action.

    The only way to provide transparency is to define default Add and Remove operations in Component. That creates a new problem: There's no way to implement Component::Add without introducing the possibility of it failing. You could make it do nothing, but that ignores an important consideration; that is, an attempt to add something to a leaf probably indicates a bug. In that case, the Add operation produces garbage. You could make it delete its argument, but that might not be what clients expect. Usually it's better to make Add and Remove fail by default (perhaps by raising an exception) if the component isn't allowed to have children or if the argument of Remove isn't a child of the component, respectively.

    Another alternative is to change the meaning of ``remove'' slightly. If the component maintains a parent reference, then we could redefine Component::Remove to remove itself from its parent. However, there still isn't a meaningful interpretation for a corresponding Add.


Implementation of Abstract Factory, Adapter, Bridge, Builder, Chain of Responsibility, Command, Composite, Decorator, Facade, Factory Method, Flyweight, Interpreter, Iterator, Mediator, Memento, Observer, Prototype, Proxy, Singleton, State, Strategy, Template Method, Visitor

previous next