From Patterns For PHP
- This is article is a stub. You can help Patterns for PHP by expanding it.
A Composite a means of assembling and organising classes such that standalone objects and object collections are members of the same class hierarchy and may be used by client code without distinguishing between them.
Imagine for a moment a book. In a book, you'll find pages, chapters, cover-pages, subsections, prefaces, etc. Of course, we don't slop them all together and call it a book: the book is divided into chapters, which are then further divided into pages. An in-memory representation of a book composite might look like this:
At first blush, it might seem logical to simply claim that a book only contains chapters, chapters only contain sections and sections only contain pages, and have each of the classes code around each of their unique interfaces. But there are common traits around all these elements, for example, they all contain text (or at least their children do). Standardize the interfaces around these common traits, and you've got a composite.
Before we demonstrate the implementation of the pattern, let's look at how client code might utilise it. Let's take the example from our introduction a bit further. After implementing each of the Book, Chapter and Page classes we would expect our client code to utilise all these objects without distinguishing between them.
Notice how Book and Chapter both have addText() methods, rather than their own addChapter() and addPage() methods. This allows us to insert the standalone page, something not possible if it was assumed that all the children of a Book were chapters. Furthermore, all the classes in the hierarchy implement output(), allowing us to show the entire book, just one chapter, or even a single page.
Those familiar with the Document Object Model may note striking similarities between our example and the standard use-case of DOM. This is intentional: DOM is an extremely good example of the composite pattern. All elements in an XML document boil down to nodes, and they can be treated uniformly whether they are elements, text, or the document root node.
- This section is actively under discussion, see Talk:Composite.
Before we can actually start writing the code, there are a few common implementation issues we have to deal with first.
Child management operators
You may have noticed that in the above code, we never executed addText() on a page object. Since it's not a composite, such an operation is not meaningful. However, the whole point about composition is that you can treat components uniformly! Gang of Four boils it down to a trade-off between "safety and transparency". You can:
- Define child operations at the very bottom of the class hierarchy, so that even the non-composite objects have it. This lets you treat all objects uniformly, but lets the client do meaningless things like
$marble->add($bag). In our case, however, we can implement an interesting fallback behavior: call output() on the passed node and then concatenate that to the existing page contents. This may not always be possible, however, and even when it is, it should be considered poor form.
- Define child operations only for Composites. While this means that it's now impossible for the client to add bag to the marble, it also means that the interfaces aren't uniform anymore.
The first possibility might look like this:
While the second possibility would look like this:
Throwing an "Attempt to call undefined function" error when addText is called on Page.
A rich base interface
The previous issue ties into a more general one, that is, how much should we stuff into the parent class? On one hand, anything we put into the base class is uniform across the entire hiearchy and can be used with impunity. On the other hand, it could mean a lot of junk methods that are meaningless for a particlar object in the hierarchy. The advice it to use good judgment and a little creativity for default behavior.
So, here's an implementation of the book class mentioned above.
Note that in this example the model and view are mixed together, which is generally considered a no-no. Find out how to seperate the HTML generation from these classes using a Visitor. You may also want to go through the book page by page, that would be implemented with an Iterator.