Categories


Loading feed
Loading feed
Loading feed

PHP Patterns: The Composite Pattern (continued)


[ continued from part 1 of PHP Patterns: The Composite Pattern ]

Now that the actual code is written, let's play the client and put the Composite pattern through its paces. Figure 3 shows a tree of classes. Here we build a tree of objects, similar to the one we modeled in Figure 1:

$bob = new Note("Bob's notes""General meanderings");
$shopping $bob->add(new Note("Weekly shop"));
$shopping->add(new Note("Oranges"));
$shopping->add(new Note("Beer""don't forget this time!"));

$bob->add(new Note("Household"));

$work $bob->add(new Note("Work stuff"));
$contract $work->add(new Note("Stevensons account"));
$contract->add(new Binary("invoice333.doc"));
$web $contract->add(new WebText("new issue""http://localhost/mattz/bugreport.html"));

$work->add(new Note("articles"))->add(new Note("Zend piece"));

$bob->output();

Although there's work involved in setting up a Composite structure, using one should be easy. It's just like snapping parts together. Assuming that both invoice333.doc and http://localhost/mattz/bugreport.html exist, the above script generates the following output:

Bob's notes
 - General meanderings
    Weekly shop
        Oranges
        Beer
         - don't forget this time!
    Household
    Work stuff
        Stevensons account
            << binary: invoice333.doc >>
            new issue
            > Stevensons Bug Report
            >
            > We are losing address details in the summary
            > display page. They are still in the database OK,
            > because I can see them in the admin environment.
            > Could you take a look?
        articles
            Zend piece
Now that we have this structure in place, we can perform some basic manipulation. We can remove nodes:

$contract->remove($web);

We can list sub-trees:

$work->output();

Or copy them wholesale:

$newwork = clone $work;

Discussion

There is something satisfying about the way that Composite pattern components combine together at runtime. It's amazing that such a simple interface can result in such relatively involved and useful structures - the whole becoming very much more than the sum of the parts. There are, however, a number of issues which should command awareness.

Perhaps the most glaring problem is the interface for leaf objects. In the Binary class we were forced to implement redundant add() and remove() methods that quietly did nothing. There is a problem here, of course. A client calling add() or remove() on a leaf object may well be indicative of a bug, yet our implementation colludes with the deluded client by issuing no warning.

One way around this problem would be to poison the add() and remove() methods by having them throw exceptions:

class Binary extends MyListComponent {
//...

    
function add(MyListComponent $comp) {
        throw new 
Exception(__CLASS__." is not a composite");
    }

    function 
remove(MyListComponent $comp) {
        throw new 
Exception(__CLASS__." is not a composite");
    }

//...

As bad smells in code go, this is relatively rank; implementing a method that only throws an exception is pretty inelegant. With this code, we accept the probability that a client will have to type test each component to avoid getting an exception when it calls one of the troublesome methods.

Since we are already reconciled to manual type checking, we could win a cleaner system by factoring add() and remove() out from MyListComponent. Once these methods are extracted from the superclass, Binary is no longer forced to implement them. The declaration for add() and remove() would now live in MyListComposite.

Here are MyListComponent and Binary, amended to exclude child operations:

abstract class MyListComponent {
    abstract function 
output($level 0);
    abstract function 
number();
    
// pad() omitted for brevity
}

class 
Binary extends MyListComponent {
    private 
$path;

    function 
__construct($path) {
        if (!
file_exists($path)) {
            throw new 
Exception("'$path' does not exist");
        }
        
$this->path $path;
    }

    function 
number() {
        return 
1;
    }

    function 
output($level 0) {
        
$pad $this->pad($level);
        
$basename basename($this->path);
        print 
"{$pad}<< binary: {$basename} >>\n";
    }
}

This has the same effect as throwing exceptions in Binary::add() and Binary::remove(), but with less work. Either way, given an array of components you'll have to type check before calling add() or remove(). Here's some code that adds a note to every composite in an array of components:

foreach ($components as $comp) {
    if (
$comp instanceof MyListComposite) {
        
$comp->add(new Note("added"));
    }
}

We use the instanceof operator to ensure that the component in question is also a composite. In C++ you would need some magic here to cast from a MyListComponent to MyListComposite, but in PHP you can simply check the type and then call the methods you have confirmed.

Coming full circle, you may wish you had let sleeping dogs lie and stayed with the silently pointless implementations of add() and remove() in the Binary class. Despite their misleading nature, they would allow a client to loop through a bunch of components without worrying about type at all. This is really a matter of choice.

Another issue to bear in mind when deploying the Composite pattern is that of storage and retrieval. Tree structures do not lend themselves well to relational databases. A naA_ve database strategy might write parent/child and child/parent references to the database. Loading a parent would kick off another query to fetch its children (by looking for all components that store the current one as their parent). This would become prohibitively expensive for larger trees, as ripples of queries propagated from node to node.

A better strategy would be to tag all components with an ID that refers to the tree as a whole. You can then acquire all components in a single query. You would still need to reconstruct the tree in post-processing, of course, which is fiddly but not difficult. One problem with this approach is that it forces you to retrieve an entire tree, unless you extend the strategy so that components can refer to more than one tree ID. You may then need a separate table in the database to accommodate this many-to-many relationship. Whichever way you look at it, you need to do a fair amount of work in order to get composites into and out of a database.

If you do not need great speed then XML is a better solution when saving composite data. Elements in an XML document are organized in a tree in exactly the same way as Composite pattern components, which makes them a natural fit. In fact, if you take a look at the DOM XML classes, you'll see that they are themselves an example of the Composite pattern.

For a quick and easy persistence you could of course simply serialize a Composite tree, but remember that XML is searchable with XPath, which can help you to look for sub-trees if required. I will look at simple way of converting a composite structure into XML in the next article in this series.

Our earlier problem with leaves versus composites illustrates a wider issue. The component interface is necessarily generic. You can create specialized functionality in child classes, but a client expects to treat all objects in the composite in the same way. One solution here would be to pull the declaration of specialized operations up to the abstract superclass (the component). This pollutes your interface, however, and should usually be avoided. Most subclasses will have no use for the methods they are then forced to implement, and the client will probably need to type test to ensure that it is working with the correct sub-type.

When you need to provide unique behavior for particular components, probably the best answer is to externalize the operation in question. You do this by creating a type specialized for working with your composite, primed to perform particular actions on particular components within the tree. I will return to this theme in detail in the next article, which will cover the Visitor pattern.

Visitor has other uses. By their nature, composite trees are well suited to traversing themselves, so it is tempting to pile all sorts of operations which are not strictly a responsibility of the type into the composite interface. We provided a basic output() method in this example, but we could also add toXml(), toHtml(), and any number of other format operations. These would follow the same model as output(), because it's a nice clean way of visiting every node of the tree below an arbitrary starting point.

This initial convenience comes at a cost, though. The component interface could easily become cluttered with methods that are not central to the purpose of the type. This is not only messy, it is also poor design. Your irrelevant operations will sooner or later demand their own variation. You could very easily find yourself subclassing components according to different modes of presentation. I have covered the problems that can occur when you subclass according to more than one force, in previous articles.

So, we need to keep the component interface as clean as possible. By providing a mechanism for an outside object to visit all the components in the tree easily, we solve this problem without the need for exposing the $children property (although you may want to do this via access methods, depending on your project's needs).

As your structure becomes more complex, you may find that you need to check types internally. The composite interface is necessarily simple: one size fits all - you can add any component using the add() method. This is fine for many purposes, but you may eventually create components that are not compatible with some composites. Say, for example, we add an Address class to MyList. You can add Address objects to Note objects, and Notes to Address objects, but what if our business constraints state that Address objects should not contain Binary or WebText objects? There is little we can do then but add type checking to the add() method in the Address class:

class Address extends MyListComposite {
    
//...

    
function add(MyListComponent $comp) {
        if (
$comp instanceof WebText || $comp instanceof Binary) {
            throw new 
Exception("Incompatible composite type");
        }
        return 
parent::add($comp);
    }
}

While this is not as neat as one would like, it does work. You would probably not wish to rely too heavily upon such devices, however.

Typical Pattern Characteristics

The clue, of course, is in the name. This pattern emphasizes the importance and power of composition. With a relatively small number of classes, we can combine objects at runtime to form complex structures.

The power of the interface is also clear here. A client that uses a Composite structure is protected from needing to know about the number and relationship of the components that sit behind single object it talks to. The fact that every component in the structure shares the same interface, means that a client can work transparently with a single component or hundreds of them - without ever needing to change its strategy.

About the Author

Matt Zandstra is a developer, teacher and writer specializing in object oriented Internet applications. He currently works as an engineer at Yahoo!, where he helps develop a core template management system. He is the author of SAMS Teach Yourself PHP in 24 Hours, and the more recent PHP 5: Objects, Patterns and Practice. He has written articles about PHP for Linux Magazine, IBM, Zend.com and bgz-consultants.com. Matt's own Web site can be found at http://www.getinstance.com.

Comments