The Composite Pattern

The Composite Pattern

You've already had a little experience with one pattern the composite from the Instrument interface in Chapter 2, "Unified Modeling Language (UML)." Take a look at the class diagram again in Figure 4-1.


Figure 4-1

Figure 4-1 shows that a DrumSet object can be composed of both Drum and Cymbal objects. Drum and Cymbal can be thought of as children of the DrumSet object. A DrumSet can be composed of any number of Drums and Cymbals. Notice, though, that both DrumSet and its children are all of the same type, namely Instrument. When a client object interacts with DrumSet, it does so through the same interface as it would to interact with its children. This relationship is known as the composite pattern.

Figure 4-2 shows the general case for the composite.


Figure 4-2

The composite pattern has two parts: the Component abstract class and the Composite, which is a concrete implementation of the Component class. All Composite objects descend from the abstract Component class. Any Component can contain other Components. A Component containing other components can be thought of as a Composite. A Component with no children can be thought of as an empty Composite.

Note 

Some implementations of Composite make a distinction between Composite objects and Leaf objects. Leaf objects are Components that can't contain children. For this example, all Components have the ability to be Composites.

Moving back from the general case, it will be necessary to make a change in the design and switch the Instrument interface to an abstract class. Interfaces and abstract classes are similar because neither one can be used directly to instantiate an object. The key difference is that an abstract class can have some fully implemented methods, whereas interfaces just have method declarations. Use abstract classes when you want to maintain the same methods in all your subclasses but have some general functionality that can be shared by the subclasses. Use interfaces when the implementations will differ across most or all the methods in subclasses. Figure 4-3 shows the new class diagram. Note that abstract class diagrams are indicated with italics.


Figure 4-3

The association line indicates that each concrete instance of AbstractInstrument may contain any number including zero of other instances of AbstractInstrument.

Implementation

The following PHP code shows the implementation of the composite pattern.

   <html>    <body>    <head>    <style>    body {font : 12px verdana; font-weight:bold}    td {font : 11px verdana;}    </style>    </head>    <?php    abstract class AbstractInstrument {      private $name;      private $category;      private $instruments = array();      public function add(AbstractInstrument $instrument) {          array_push($this->instruments, $instrument);      }      public function remove(AbstractInstrument $instrument) {          array_pop($this->instruments);      }      public function hasChildren() {        return (bool)(count($this->instruments) > 0);      }      public function getChild($i) {        return $instruments[i];      }      public function getDescription() {        echo "- one " . $this->getName();        if ($this->hasChildren()) {          echo " which includes:<br>";          foreach($this->instruments as $instrument) {             echo "<table cellspacing=5 border=0><tr><td>&nbsp;&nbsp;&nbsp;                   </td><td>-";             $instrument->getDescription();             echo "</td></tr></table>";          }        }      }      public function setName($name) {        $this->name = $name;      }      public function getName() {        return $this->name;      }      public function setCategory($category) {        $this->category = $category;      }      public function getCategory() {        return $this->category;        }    }    class Guitar extends AbstractInstrument {      function __construct($name) {        parent::setName($name);        parent::setCategory("guitars");      }    }    class DrumSet extends AbstractInstrument {      function __construct($name) {        parent::setName($name);        parent::setCategory("drums");      }    }    class SnareDrum extends AbstractInstrument {      function __construct($name) {        parent::setName($name);        parent::setCategory("snare drums");      }    }    class BaseDrum extends AbstractInstrument {      function __construct($name) {        parent::setName($name);        parent::setCategory("base drums");      }    }    class Cymbal extends AbstractInstrument {      function __construct($name) {        parent::setName($name);        parent::setCategory("cymbals");      }    }    $drums = new DrumSet("tama maple set");    $drums->add(new SnareDrum("snare drum"));    $drums->add(new BaseDrum("large bass drum"));    $cymbals = new Cymbal("zildjian cymbal set");    $cymbals->add(new Cymbal("small crash"));    $cymbals->add(new Cymbal("large high hat"));    $drums->add($cymbals);    $guitar = new Guitar("gibson les paul");    echo "List of Instruments: <p>";    $drums->getDescription();    $guitar->getDescription();    ?>    </body>    </html> 

Notice that all the concrete instruments, such as DrumSet and Guitar, descend from the AbstractInstrument class. Also notice that all the subclasses inherit the implemented methods in the abstract class. To implement the getDescription() , the current instrument is checked to see whether it has children. If it does, the method is called recursively until it travels through the entire tree.

Historically (in PHP4), the & symbol caused the object, in this case of type AbstractInstrument, to be passed by reference:

   public function add(AbstractInstrument & $instrument) { 

This is important because otherwise an entirely new local copy would be used within the function. However, because PHP5 automatically passes objects by reference, the & is not required. For our purposes this behavior is desired because we actually want to work on the original object rather than on a copy of it. Figure 4-4 shows the object diagram for the previous code after the instruments and their child instruments have been assembled. Object diagrams are similar to class diagrams except that they show instances of objects indicated with their names underlined. Also, they represent the object relationships in the system at a point in time.


Figure 4-4

In this example, the concrete instrument classes don't really differ in great detail except for overriding the getDescription() method in the Guitar class. Consider, though, if you needed to add methods to contact a manufacturer to get stocking information. If each manufacturer had a favored way to receive contact from the system, you could specify that in a contact() method. First you would add an abstract method to AbstractInstrument and then you would implement it in each subclass. Adding the abstract method ensures that you won't forget to implement it in the subclasses. If you don't, you'll get an error.

If you were certain that no additional functionality needed be added to your instruments, you could just create a GenericInstrument class that inherited from AbstractInstrument. This way when you created, say, a cymbal, you would say:

   $cymbals = new GenericInstrument("zildjian cymbal set"); 

The key here is that anyone accessing the Instrument interface be it you, another programmer, or another part of the application doesn't need to know how it was implemented. Calling getDescription() returns a description of the tree structure of an Instrument whether it has children or not, or its children have children, and so on. The caller of the method doesn't need to know whether it has children.

Because the interface is the same for all instrument objects but they do not respond in the same way, the instrument objects can be thought of as polymorphic. The basic description of polymorphism is same interface, different implementation or, more generally, same interface, different behavior.

Considerations

The composite pattern described here is very flexible; there are no constraints on which instrument may have children. Consider adding the following line:

   $cymbals->add(new Cymbal("large high hat"));    $drums->add($cymbals);    $cymbals->add($drums);    $guitar = new Guitar("gibson les paul"); 

Adding the drums to the cymbals that already belong to drums creates a circular reference. When calling getDescription() a line like this can crash a Web server. You might want to safeguard against such mistakes. One strategy would be to check during the add() method to see whether the instrument being added already contains a reference to the one calling the method. If so, you could report an error.

You also might want to experiment with group of instruments that cannot have things added to them. One option is to individually go to each class definition and override the add() method so that it does nothing. Another option is to create a new abstract class from which all single instruments descend. This abstract class could have an add() method that either does nothing or reports an error. This is more in keeping with the composite pattern discussed previously, in which two classes decend from Component. One is a Leaf that cannot have children and the other is a Composite that can. The class diagram for this is shown in Figure 4-5.


Figure 4-5

One other consideration is constraining certain instruments from being added to composites. Perhaps you don't want to allow a Guitar object to be added to a DrumSet. In that case you could define certain legal types based on the category attribute. Implementing these features are all worthwhile exercises to try on your own.



Professional PHP5 (Programmer to Programmer Series)
Professional PHP5 (Programmer to Programmer Series)
ISBN: N/A
EAN: N/A
Year: 2003
Pages: 182
BUY ON AMAZON

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net