- Compose objects into tree structures to represent part-whole hierarchies.
- Composite lets clients treat individual objects and compositions of objects uniformly.
- The Composite design pattern is an in-memory data structures with groups of objects, each of which contain individual items or other groups.
- A tree control is a good example of a Composite pattern.
- The nodes of the tree either contain an individual object (leaf node) or a group of objects (a sub-tree of nodes).
- All nodes in the Composite pattern share a common interface which supports individual items as well as groups of items.
- This common interface greatly facilitates the design and construction of recursive algorithms that iterate over each object in the Composite collection.
Participants
Component
(IElement)
- Declares the interface for objects in the composition.
- Implements default behavior for the interface common to all classes.
- Declares an interface for accessing and managing its child components.
- (Optional) defines an interface for accessing a component's parent in the recursive structure, and implements it if that's appropriate.
Leaf
(PrimitiveElement)
- Represents leaf objects in the composition.
- Defines behavior for primitive objects in the composition.
Composite
(CompositeElement)
- Defines behavior for components having children.
- Stores child components.
- Implements child-related operations in the Component interface.
var root = new CompositeElement { Name = "Picture" };
root.Add(new PrimitiveElement { Name = "Red Line" });
root.Add(new PrimitiveElement { Name = "Blue Circle" });
root.Add(new PrimitiveElement { Name = "Green Box" });
var childNode = new CompositeElement { Name = "Two Circles" };
childNode.Add(new PrimitiveElement { Name = "Black Circle" });
childNode.Add(new PrimitiveElement { Name = "White Circle" });
root.Add(childNode);
root.Display();
public interface IElement
{
string Name { get; set; }
void Add(IElement node);
void Remove(IElement node);
void Display();
}
public class CompositeElement : IElement
{
public string Name { get; set; }
private List<IElement> _Nodes = new List<IElement>();
public void Add(IElement node) => _Nodes.Add(node);
public void Remove(IElement node) => _Nodes.Remove(node);
public void Display()
{
Console.WriteLine(Name);
foreach (IElement node in _Nodes)
node.Display();
}
}
public class PrimitiveElement : IElement
{
public string Name { get; set; }
public void Add(IElement node) => Console.WriteLine("Cannot add to a PrimitiveElement");
public void Remove(IElement node) => Console.WriteLine("Cannot remove from a PrimitiveElement");
public void Display() => Console.WriteLine(Name);
}
Rules of Thumb
- Composite and Decorator have similar structure diagrams, reflecting the fact that both rely on recursive composition to organize an open-ended number of objects.
- Composite can be traversed with Iterator. Visitor can apply an operation over a Composite.
- Composite could use Chain of Responsibility to let components access global properties through their parent.
- It could also use Decorator to override these properties on parts of the composition.
- It could use Observer to tie one object structure to another and State to let a component change its behavior as its state changes.
- Composite can let you compose a Mediator out of smaller pieces through recursive composition.
- Decorator is designed to let you add responsibilities to objects without subclassing.
- Composite's focus is not on embellishment but on representation.
- These intents are distinct but complementary.
- Consequently, Composite and Decorator are often used in concert.
- Flyweight is often combined with Composite to implement shared leaf nodes.
Opinions
- The whole point of the Composite pattern is that the Composite can be treated atomically, just like a leaf.
- If you want to provide an Iterator protocol, fine, but it might be outside the pattern itself.
- At the heart of this pattern is the ability for a client to perform operations on an object without needing to know that there are many objects inside.
- Being able to treat a heterogeneous collection of objects atomically (or transparently) requires that the "child management" interface be defined at the root of the Composite class hierarchy (the abstract Component class).
- However, this choice costs you safety, because clients may try to do meaningless things like add and remove objects from leaf objects.
- On the other hand, if you "design for safety", the child management interface is declared in the Composite class, and you lose transparency because leaves and Composites now have different interfaces.
- Smalltalk implementations of the Composite pattern usually do not have the interface for managing the components in the Component interface but in the Composite interface.
- C++ implementations tend to put it in the Component interface.
- This is an extremely interesting fact, and one that I often ponder.
- I can offer theories to explain it, but nobody knows for sure why it is true.
- My Component classes do not know that Composites exist.
- They provide no help for navigating Composites, nor any help for altering the contents of a Composite.
- This is because I would like the base class (and all its derivatives) to be reusable in contexts that do not require Composites.
- When given a base class pointer, if I absolutely need to know whether or not it is a Composite, I will use
dynamic_cast
to figure this out.
- In those cases where
dynamic_cast
is too expensive, I will use a Visitor.
- Common complaint: "if I push the Composite interface down into the Composite class, how am I going to enumerate (i.e. traverse) a complex structure?"
- My answer is that when I have behaviors that apply to hierarchies like the one presented in the Composite pattern, I typically use Visitor, so enumeration isn't a problem.
- The Visitor knows in each case, exactly what kind of object it's dealing with.
- The Visitor doesn't need every object to provide an enumeration interface.