Composite Design Pattern
Classic GOF Structural Design Patterns
Intent
The Composite Pattern is used where we need to treat a group of objects in similar way as a single object. The Composite pattern composes objects in term of a tree structure to represent part as well as whole hierarchy.
It is used when:
- you want to represent part-whole hierarchies of objects
- you want client to be able to ignore difference between compositions of objects and individual objects. Clients will treat all objects in the composite structure uniformly
Problem
It is useful when an application needs to manipulate a hierarchical collection of "primitive" and "composite" objects. Processing of a primitive object is handled one way, and processing of a composite object is handled differently. Having to query the "type" of each object before attempting to process it is not desirable.
Discussion
The Composite Pattern is basically a partitioning design pattern and describes a group of objects that is treated the same way as a single instance of the same type of object. The intent of a composite is to “compose” objects into tree structures to represent part-whole hierarchies. It allows you to have a tree structure and ask each node in the tree structure to perform a task.
When dealing with Tree-structured data, programmers often have to discriminate between a leaf-node and a branch. This makes code more complex, and therefore, error prone. The solution is an interface that allows treating complex and primitive objects uniformly. In object-oriented programming, a composite is an object designed as a composition of one-or-more similar objects, all exhibiting similar functionality. This is known as a “has-a” relationship between objects
When thinking about this pattern, it might be useful to think of a real world scenario. Consider an organization that has a Director. Under that Director are one or more Senior Managers. Under each Senior Manager are one or more Managers...and, under them are developers. This is a classic hierarchy. The objects in this hierarchy are very similar but they are just different enough that using them would possibly require treating them differently in code. The Composite Pattern allows you to treat these objects in a consistent manner.
The key concept for this pattern is that you can manipulate a single instance of the object just as you would manipulate a group of them. The operations you can perform on all the composite objects often have a least common denominator relationship.
Structure
There is not really much to the structure of a Composite design pattern. It consists of treating each node in two ways - Composite or Leaf. A Composite means it can have other objects below it. A Leaf means it has no objects below it.
What makes the Composite pattern one of the most beautiful is the power of recursion. This is simply explained with the simple organization example such as that in the DISCUSSION above. You want to find the total salary paid to all employees of the organization. It is nothing but the salary of CEO + the salary paid to all the departments. What is the salary of a department? It is the salary of the department head + the salary of all projects. What is the total salary of a project? It is the salary of the project manager + the salary of all the project members. In short, the salary of anything is the salary of self + the salary of all its sub groups.
The Client use the component class interface to interact with objects in the composition structure. If the recipient is a leaf, then request is handled directly; if recipient is a composite,then it usually forwards the request to its child components - possibly performing additional operations before and after forwarding.
As you can see in the diagram above, the the structure is straightforward and consists of:
- Component - declares interface for objects in composition, implements the default behavior for the interface common to all classes as appropriate, and declares an interface for accessing and managing its child components
- Composite - defines behavior for components having children, stores the children objects, and implements child related operations in the component interface.
- Leaf - represents leaf objects in the composition (a leaf has no children) and defines the behavior for primitive objects in the composition.
- Client - manipulates objects in the composition through the component interface.
Practical Example
For a practical example of the Composite Pattern in a real world usage scenario, lets focus on a configuration management system.
Our Configuration Management System will actually consist of a simple hierarchy - machines will have a configuration (Leaf), and groups (composites) can also have a configuration. Any machine can have its own specific configuration; however, the machine can also inherit configuration settings from the group that it is part of. This inheritance of configuration settings can flow all the way up to the top of configuration tree.
Let's walk through an example of how this would work. The top level group (Root in the diagram below) may contain some basic settings that should be used by all members of the hierarchy unless specifically over-ridden. Any machines directly attached to this group will inherit the group configuration settings and then may, or may not, have its own as well. Group 2 in the diagram will inherit configuration settings from the root group and may have its own settings as well. Any machines attached to Group 2 (in this case Machine 2) will start with the settings in the root group, added to the settings in Group 2, and finally, any settings of its own. This pattern repeats regardless of the depth of the hierarchy.
Much like the generic diagram in STRUCTURE above, the Composite will provide a convenient way of handling the objects in the hierarchy in a uniform way.
Let's expand on this practical example with some code below.
Code Samples
Below, you will find examples of the Composite implemented in four different commonly used languages. Each one of the examples implements the exact same scenario as described in the PRACTICAL EXAMPLE section above.
Each sample will use a Composite to allow for easily accessing and manipulating a hierarchy of objects that are similar but different.
C# Composite Code Sample
In this C# example, the Composite pattern is used
Java Composite Code Sample
In this Java example, the Composite pattern is used.
Typescript Composite Code Sample
In this TypeScript example, the Composite pattern is used
JavaScript Composite Code Sample
In this JavaScript example, the Composite pattern is used.
Considerations
- Ensure that your problem is about representing "whole-part" hierarchical relationships.
- "Containers that contain containees, each of which could be a container." Divide your domain concepts into container classes, and containee classes.
- Create a "lowest common denominator" interface that makes your containers and containees interchangeable. It should specify the behavior that needs to be exercised uniformly across all containee and container objects.
- All container and containee classes declare an "is a" relationship to the interface.
- All container classes declare a one-to-many "has a" relationship to the interface.
- Container classes leverage polymorphism to delegate to their containee objects.
- Child management methods [e.g. addChild(), removeChild()] should normally be defined in the Composite class. Unfortunately, the desire to treat Leaf and Composite objects uniformly may require that these methods be promoted to the abstract Component class. See the GoF book for a discussion of these "safety" versus "transparency" trade-offs.
Additional Notes
- 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 sub-classing. 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.
- Composite pattern should be applied only when the group of objects should behave as the single object.