The Decorator Pattern

The Decorator Pattern

The two concrete widgets created for the observer pattern have a different appearance. If you needed to add a new style of widget, you would subclass Widget and implement the draw() method to write the HTML. Say that needed to add a feature to all existing widgets, such as a border. You could go into each draw() method and add some more HTML to each, but then the border would be hard-coded and all widgets would be forced to have a border. You could create a new set of subclasses of existing concrete widgets that implemented the border in the draw() method. If you had only two widgets, as in the Observer example, this might be an option you would end up with four widgets. However, if you had five widgets to start with, it might not seem so appealing. Suddenly you have a lot of widget classes to worry about.

There is another way to handle situations like this, one that doesn't require you to create a new subclass for every widget you want to add a border to. Using the decorator pattern allows you to add features or functionality to existing objects without using inheritance. Figure 4-9 shows the class diagram for the decorator pattern.

Figure 4-9

The class diagram indicates that Decorators are a type of Widget, too. This is good because we don't want to change the way we access a Widget, whether it's decorated or not. Take a look at the code of the following Decorator, saved as border_decorator.php, that draws a border around a Widget.

   <?php    require_once("abstract_widget.php");    class BorderDecorator extends Widget {      private $widget;      function __construct(Widget $widget) {             $this->widget = $widget;      }      public function draw() {             $this->widget->update($this->getSubject());                echo "<table border=0 cellpadding=1 bgcolor=#3366ff>";                echo "<tr bgcolor=#ffffff><td>";                $this->widget->draw();                echo "</td></tr></table>";        }    }    ?> 

In this example we've moved all the classes and interfaces into their own files and used require_once to import them. The draw method generates some HTML to draw a table. Notice that in the middle of drawing this border table, the draw() method calls the draw() method of the Widget that was passed in the constructor. Because of this, the Widget gets some extra "decoration'' around it, namely, the border. An example of how you use the Decorator follows:

   $widgetA = new BasicWidget();    $widgetA = new BorderDecorator($widgetA); 

After the decorator pattern is set up, you need only to pass it a Widget object. Then you can use the decorated Widget exactly the same way as you would a regular one. Figure 4-10 shows the BasicWidget object with and without the BorderDecorator applied to it.

Figure 4-10


There are some small but important changes in the implementation of the Widget objects to accommodate the decorator pattern. First, the protected InternalData array in the abstract Widget class has been changed to a private reference to the DataSource object stored in the $subject variable.

   abstract class Widget implements Observer {      private $subject;      abstract public function draw();      public function update(Observable $subject) {             $this->subject = $subject;      }      public function getSubject() {             return $this->subject;      }    } 

Next, during the implemented draw() methods in each concrete Widget class, the Widget accesses its subject by calling its inherited getSubject() method.

   public function draw() {           $data = $this->getSubject()->getData();           $numRecords = count($data[0]);           $html = "<table border=1 width=130>";           $html .= "<tr><td colspan=3 bgcolor=#cccccc>                          <b>Instrument Info<b></td></tr>";           for($i = 0; $i < $numRecords; $i++) {                  $instms = $data[0];                  $prices = $data[1];                  $years = $data[2];                  $html .= "<tr><td>$instms[$i]</td><td> $prices[$i]                                </td><td>$years[$i]</td></tr>";                  }           $html .= "</table>";           echo $html;    }    } 

Finally, because the Decorator class is what is actually assigned as an observer of the DataSource object, it must in turn pass the DataSource down to its Widget object when the Decorator's draw() method is called. This allows the Widget object to have access to the data.

   public function draw() {             $this->widget->update($this->getSubject());             echo "<table border=0 cellpadding=1 bgcolor=#3366ff>";             echo "<tr bgcolor=#ffffff><td>";             $this->widget->draw();             echo "</td></tr></table>";    } 

Using the Decorator

After you've done the work developing the Decorator classes, you'll find that it is a very flexible and powerful pattern. Not only is it easy to use, but different Decorators can be combined to give multiple effects to the same widget. Decorators can even be applied in different order. Another decorator class follows. Save the file as closebox_decorator.php:

   <?php    require_once("abstract_widget.php");    class CloseBoxDecorator extends Widget {      private $widget;      function __construct(Widget $widget) {             $this->widget = $widget;      }      public function draw() {             $this->widget->update($this->getSubject());             print "<table border=0 cellspacing=1 bgcolor=#666666>";             print "<tr bgcolor=#666666>";             print "<td align=right>";             print " <table width=10 height=10 bgcolor="cccccc">";             print " <tr><td><b>x</b></td></tr>";             print " </table>";             print "</td>";             print "</tr>";             print "<tr bgcolor=#ffffff>";             print "<td>";             $this->widget->draw();             print "</td>";             print "</tr>";             print "</table>";     }    } 

The preceding Decorator applies a simple menu bar with a close box on it. Combining the two Widgets with the two Decorators is easy. Save this file as decorator.php:

   <?php    require_once ("abstract_widget.php");    require_once ("closebox_decorator.php");    require_once ("border_decorator.php");    require_once ("observable.php");    $dat = new DataSource();    $widgetA = new BasicWidget();    $widgetB = new FancyWidget();    $widgetB = new BorderDecorator($widgetB);    $widgetB = new CloseBoxDecorator($widgetB);    $widgetA = new CloseBoxDecorator($widgetA);    $widgetA = new BorderDecorator($widgetA);    $dat->addObserver($widgetA);    $dat->addObserver($widgetB);    $dat->addRecord("drum", "$12.95", 1955);    $dat->addRecord("guitar", "$13.95", 2003);    $dat->addRecord("banjo", "$100.95", 1945);    $dat->addRecord("piano", "$120.95", 1999);    $widgetB->draw();    echo "<br>";    $widgetA->draw();    ?> 

Notice that the first Widget has its Decorators applied in one order, and the second has then applied in the opposite order. Figure 4-11 shows the result of this code.

Figure 4-11


The Decorators in this section all have hard-coded color values. There's no reason that you can't alter them to be more flexible. The BorderDecorator class can be changed to allow for a specified border width and color. You can use some DHTML to make the CloseBoxDecorator actually hide itself when the close box is clicked.

Remember that the decorator pattern need not be limited to purely graphical elements. In the case of the Widgets, the decorator pattern generated additional HTML to alter their look. You could instead have a data structure of some kind and use a Decorator to add additional information to it. For example, if you had a fragment of XML, you could use a Decorator to embed it in a larger XML structure.

To test your understanding of object-oriented programming, you might want to refine the way the DataSource is passed down to the Widget object from the Decorator object. There are several different ways to handle this. How could the Widget get a copy of the DataSource object without requiring the Decorator to pass it down in the draw() method?

Professional PHP5 (Programmer to Programmer Series)
Professional PHP5 (Programmer to Programmer Series)
Year: 2003
Pages: 182
BUY ON AMAZON © 2008-2017.
If you may any questions please contact us: