There are many issues to consider when implementing the Composite pattern:
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:
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.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 { // ... };
Similar tests for a Composite can be done using the C++ dynamic_cast construct.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 }
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.