The composite structural pattern allows you to build elaborate objects from smaller objects and not worry about how big they get. You can treat your composite objects as if they are all the same.
If you have hierarchical data, then consider this design pattern as a way to help you work with objects that represent roots of the hierarchy without worrying about if there is just a single item in the hierarchy or thousands.
You start out with an abstract class that allows you to treat all the classes participating in this pattern the same. We’ll call this class a Component. Then let’s say you want to represent a file system with files and folders. All you need to do is define methods in the Component class that will be common for files and folders. Then declare the File and Folder classes to each derive from the Component class and override the methods that make sense to them.
Some of the methods may only apply to files, some only to folders, and some methods may apply to both. The Folder class will be most interested in the child manipulation methods. Each of these methods will work with Component instances so that you can add a File or a Folder with the same method. This is the key to this pattern – being able to treat all the objects as if they are of the same type.
A file system example follows the hierarchy very well, but it may not appear at first to be the best example of this pattern because files and folders usually behave very differently. It can still work as an example as long as you remember that what you see as the end user of an application doesn’t always match what the code sees. The code can still treat files and folders the same even if it also displays them to you as if they’re different.
Listen to the full episode or read the full transcript below.
The basic description says that this pattern lets you compose hierarchical tree structures and then treat the individual objects and the compositions of those objects the same.
You should consider this pattern anytime you have objects that can contain other objects which can then contain other objects etc. Think of your computer’s file system where you have folders that can contain files as well as other folders. Any folder no matter how deep it is follows these same rules and can keep containing more files and folders. Sometimes, there might be a limit imposed on this like how Windows machines have a maximum file path length but that doesn’t mean you have to add limits to your design.
Sometimes files can behave like folders like when you navigate into a compressed file. This pattern doesn’t say that you can’t tell objects apart, just that you can treat them the same. Just because the code can treat files and folders the same doesn’t mean that the application needs to let the user do the same thing.
Maybe you want to add a feature to your application that lets users compose notes by typing some text. You want the user to be able to add as many notes as desired. And you also want to let the user refine the notes by adding more details. Each detail will be another note. Well, once a note can contain another note, it’s just a small extra step to let a note contain many other notes.
This is the basis for the composite pattern. All the notes are the same even if some of them could be rather large. I’ll explain how to do this in code right after this message from our sponsor.
( Message from Sponsor )
The first step to implement this pattern is to define an abstract class with methods common to everything you’re going to want to do with the items in the tree.
Let me first say that I’m using the word tree here as a general description of the hierarchy. I don’t mean to imply this is a binary tree. Episode #41 describes binary trees.
You’ll probably have two types of methods in your abstract class. Let’s call the abstract class the Component class. If your tree will have items in it that cannot contain further items, then these are the leaves and you can name these classes whatever you want. For the file system example, this would be the File class. The first type of methods in the Component class will be the methods that apply to the leaves such as the File class. The File class inherits from the Component class and can override these methods. You might have methods to get and set a file’s name for example.
The other type of methods in the Component class will be the methods designed to work with child items. In addition to the File class, you will need a Folder class which will also inherit from the abstract Component class. The Folder class will need methods to add, remove, and get children. It’s important to note that these child manipulation methods all work with Components. This is because when you add a child, that child item could be a File or another Folder. Since both File and Folder inherit from Component, then the Component class is common.
This is the most important aspect of this pattern. You can treat any item in the tree as a Component.
Now what do you do when an item is added? Well, that’s up to you, really. The pattern just says that some of the classes which inherit from the Component class act as composites and can contain other Components. Our Folder class is playing the role of a composite. This collection could be an array, a list, a dictionary, etc. Make sure to check out episodes 39 through 46 for a review of various collection types.
You don’t really have to use a collection type though. This is common because you normally want to allow as many children as the user adds. But maybe you have a unique requirement where each composite should only have up to 3 components. You may not need a general purpose collection and maybe you want to number your children and call them child1, child2, and child3. If you have this requirement, then it’s probably because each of the children have a special meaning and if so, then I encourage not you to name them with numbers but name them with a more descriptive name.
You might find that some methods apply to both leaves and composites. Methods to get and set a file name apply equally to folders. You can provide a default implementation of the methods in the Component class and let the File and Folder class each override the methods that make sense for them.
And if you do need to be able to tell the difference between files and folders when all you have to work with are instances of the base component class, then consider adding a method to the Component class called isFolder or something like that. The default implementation can return false. And the real Folder class can then override this method to return true. This will allow you to still treat everything as if it was just a Component but still be able to tell the difference when you need to.