Design Patterns: Grouping things with the Composite Pattern
This is the sixth instalment of my series on design patterns in software. For the introduction to the whole series, definitely check out Design Patterns: Introduction with Singletons. This initial post describes general information about design patterns as well. The complexity of the composite pattern is slightly higher compared to the previous command pattern, because you end up creating hierarchies or trees.
The composite pattern is a structural design pattern that can be used when a group of objects can be treated the same as a single object in that group. It can be used to create hierarchies of objects and groups of objects, quickly turning the structure into a tree with leaves (objects) and composites (sub-groups).
As per usual, a simple UML diagram showing the outline of this pattern:
As you can see, I have shown two basic structures for an implementation here. You can implement this pattern by using an interface for Component, or an abstract class. The abstract class is shown in the book Head First Design Patterns by the Gang of Four. I have seen the interface type a lot as well.
In this sample, a component is either a leaf (object) or a composite of components (a group of objects and groups). It's a structure that can be drawn out as a tree;
First and foremost - when you want to represent part-whole hierarchies of objects, the composite pattern is your thing. As shown above in the class diagram and the little tree diagram, you can see it is easy to create a hierarchy of objects in a tree-like structure. This way, instead of letting each node in the hierarchy have a list of children, only the compositions will have children. You can treat compositions exactly the same way as the leaf nodes, but you can still distinguish them if you need to.
If you want client code to ignore the differences between individual objects and compositions of objects, the composite pattern is also quite useful. Both individual objects and compositions of objects implement the IComposite interface (or extend the Composite abstract class, depending on implementation). This way, both objects and composites can be treated equally.
Dealing with hierarchies
As stated above, when you want to deal with hierarchies of entities, it can be easy to represent them in some tree form in which you can use iterators to go through all the nodes. When you use this pattern, and you want to move a whole composition to the left, you'd simply call
MoveLeft() on the composition. The composition will then do the same for all of its children. The compositions in that list will then do the same for their children, and so forth.
Again with the open/closed principle. This pattern makes it very easy to add new forms of compositions or simply types of objects in the compositions. As per usual, it is as easy as defining a new class that implements or extends the correct interfaces or classes.
Example in TypeScript
In the example I basically produced a bookmark system with entries and categories. The view of this composition displays categories in alphabetical order and below that all the entries in the same category in alphabetical order. When you click a blue row, it will navigate to that category and display the items in there.
You can also navigate back when you're not in root, by clicking the back button. Aside from that, more navigation exists in the form of a breadcrumb above the table.
The example could easily be extended with features for moving components from one category to another, by simply removing a component from the list in one category and adding it to another again. Updating a reference is enough for moving a whole node and all children to another node.
Adding code for creating new bookmarks and categories shouldn't be a problem either, as you're basically simply adding objects to an array of the parent node that is a composition.
I have drawn the UML diagram to show that the pattern can be held in a separate package, so that it can easily be ported over to other systems at a later point. The application merely uses the package. The package is a dependency.
Check out this pen.
I hope that I managed to explain this pattern well, as it is one of my favourite ones. I love feedback, so let me know if I need to change anything or adjust the format. I want this to be as educational and understandable as it can be! I'll write about the Factory pattern next, which could clear up some object creation code...