The Composite pattern is useful whenever one has a class which may contain instances of itself. Common computer science examples are nodes in a tree structure, or shapes in a graphical model. More concrete physical world examples include regions (which can potentially contain many smaller regions), schools of fish (where those fish may swim, eat, etc in a collective fashion), and divisions/teams in a workplace.
The key feature of this pattern is a subclass which both extends the superclass, and contains (though an aggregation or composition relationship) a number of instances of the superclass.
The advantage gained from this structure is that we can transparently use a Composite Instance just as with a Primitive instance. shape.draw(), for example could draw any shape, from a primitive to a highly complex diagram or model.
- Interface (Abstract class) that represents Component.
- Concrete Composite class that implements Component.
- Concrete leaf class that implements Component.
- Client class.
- Composite maintains a collection of Components. E.g, Collection<Component>
- Component has abstract methods to add and remove an Component to and from.
- Composite has concrete method to add an instance of Component that shared by Composites and leaves on the collection.
- Composite has concrete method to remove an instance of Component that shared by Composites and leaves from the collection.
- Composite contains both Composite and leaf as Component.
- All Composites and leaves are seen as Component by Client.
- You want to represent a hierarchy of parts and whole objects.
- You want clients to not know about whether they are dealing with a composition of objects or a single object.
Recognising the pattern
Classes: Composite, Component, multiple Leaves
- Inheritance hierarchy that has a Component interface
- Component interface has declarations for add() and remove() methods for adding/removing Components.
- Composite class has private collection of Components.
- Primitive objects can be composed into more complex objects, which in turn can be composed into even more complex objects. Clients treat primitive objects and composite objects in the same way.
- Simplifies the client because it can treat simple objects and composites the same way.
- Makes it easy to add new components or composites because the client code will not be affected.
- Can make the design too general because it is hard to restrict what components can be added to composites.
- Chain of Responsibility: This pattern often uses a component-parent link.
- Decorator: This pattern is often used together with Composite.
- Flyweight: This pattern lets you share components.
- Iterator: This pattern can be used to traverse composites.
- Visitor: This pattern can be used to pull out behavior that would usually be in the Composite and Leaf classes.
- Avoid no-op overrides: The Composite pattern tells us to put the addChild(), removeChild() and getChildren() methods in the Component class. However, this means that we need to override them using a no-op in the leaf component which violates Riel's heuristic Avoid no-op overrides and smells a little of Refused bequest smell.
There are several ways that Composite can be implemented, each of which seems to break some heuristics.
If we follow the Gang of Four design, we get no-op overrides in the concrete leaf classes. This conflicts with Avoid no-op overrides. Another problem with this option is that the contract for the addChild() and removeChild() methods is very loose because we can't guarantee that children were actually removed or added if we happen to call that method on a leaf rather than a composite.
If we move the addChild(), removeChild() and getChildren() into the composite class to avoid the no-op overrides, clients now have to be aware of the difference between leaves and composites. This means that if they want to add a child to a composite, they have to make sure they have a composite and potentially need to use downcasts to get one. This violates the Avoid downcasting heuristic. On the other hand, at least clients can now be sure that the child will actually be added to the composite unlike in the previous option.
Wal's preferred option is to provide a method asLeaf() and asComposite() in the Component class. These by default return null and are overridden in the leaf and composite class to return a leaf or a composite as appropriate. This means that clients can check if they have a composite before adding children to it. This avoids downcasting and provides a safer way for the client to a composite object.
However, this option violates the Dependency inversion principle because the superclass has to know about the base classes and every time we add a base class, we will have to add another asX() method. However, in reality it is fairly unlikely that we will require something other than composites and leaves.
- Interpreter - Similar to composite, but for use with Behavioral concepts. Often used for language parsing.
Creational: Abstract Factory | Builder | Factory Method | Prototype | Singleton