The composite design pattern reduces the cost of an implementation that handles data represented as a tree. When an application does a process on a tree, usually the process has to handle the iteration on the components, the move on the tree and has to process the nodes and the leafs separately. All of this creates a big amount of code. Suppose that you have to handle a file system repository. Each folders can contain files or folders. To handle this, you have an array of items that can be file or folder. The files have a name and the folders are arrays. Now you have to implement a file search operation on the whole folder tree. The pseudo-code should look like this:
method searchFilesInFolders(rootFolder, searchedFileName) is input: a list of the content of the rootFolder. input: the searchedFileName that should be found in the folders. output: the list of encountered files. Empty the foundFiles list Empty the parentFolders list Empty the parentIndices list currentFolder := rootFolder currentIndex := 0 Add rootFolder to parentFolders while parentFolders is not empty do if currentIndex is out of currentFolder then currentFolder := last item of parentFolders Remove the last item of parentFolders currentIndex := last item of parentIndices + 1 Remove the last item of parentIndices else if the item at the currentIndex of the currentFolder is a folder then currentFolder := the folder Add currentFolder to parentFolders Add currentIndex to parentIndices currentIndex := 0 otherwise if the name of the file is equal to the searchedFileName then Add the file to foundFiles Increment currentIndex Return the foundFiles
In the previous code, each iteration of the same while loop is a process of one node or leaf. At the end of the process, the code move to the position of the next node or leaf to process. There are three branches in the loop. The first branch is true when we have processed all the children of the node and it moves to the parent, the second goes into a child node and the last process a leaf (i.e. a file). The memory of the location should be stored to go back in the tree. The problem in this implementation is that it is hardly readable and the process of the folders and the files is completely separate. This code is heavy to maintain and you have to think to the whole tree each moment. The folders and the files should be called the same way so they should be objects that implements the same interface.
- is the abstraction for all components, including composite ones.
- declares the interface for objects in the composition.
- (optional) defines an interface for accessing a component's parent in the recursive structure, and implements it if that's appropriate.
- represents leaf objects in the composition.
- implements all Component methods.
- represents a composite Component (component having children).
- implements methods to manipulate children.
- implements all Component methods, generally by delegating them to its children.
So now the implementation is rather like this:
interface FileSystemComponent is method searchFilesInFolders(searchedFileName) is input: the searchedFileName that should be found in the folders. output: the list of encountered files. class File implementing FileSystemComponent is method searchFilesInFolders(searchedFileName) is input: the searchedFileName that should be found in the folders. output: the list of encountered files. if the name of the file is equal to the searchedFileName then Empty the foundFiles list Add the file to foundFiles Return the foundFiles otherwise Return an empty list class Folder implementing FileSystemComponent is field children is The list of the direct children. method searchFilesInFolders(searchedFileName) is input: the searchedFileName that should be found in the folders. output: the list of encountered files. Empty the foundFiles list for each child in children Call searchFilesInFolders(searchedFileName) on the child Add the result to foundFiles Return the foundFiles
As you can see, a component can be an individual object and also can be a collection of objects. A Composite pattern can represent both the conditions. In this pattern, one can develop tree structures for representing part-whole hierarchies.
The best example of use of this pattern is the Graphical User Interface. The widgets of the interface are organized in a tree and the operations (resizing, repainting...) on all the widgets are processed using the composite design pattern.
This pattern is one of the least expensive patterns. You can implement it each time you have to handle a tree of data without worrying. There is no bad usage of this pattern. The cost of the pattern is only to handle the children of a composite but this cost would be required and more expensive without the design pattern.
You have to create an almost empty interface and implement the management of the composite children. This cost is very low.
You can't get caught in the system. The only relatively expensive situation occurs when you have to often change the operations applied to the whole data tree.
You should remove the pattern when you remove the data tree. So you just remove all in once. This cost is very low.
- Put the composite and component terms in the name of the classes to indicate the use of the pattern to the other developers.
Various examples of the composite pattern.