The Observer Pattern

The Observer Pattern

Often, you'll have data in your application that changes over time. Say that you have some GUI components that are required to show this data and update it when it changes. How would you handle it? One solution might be to pass the newly updated data to a method of the GUI component so that it could redraw the information. A problem with this approach is remembering to do that each time the data is updated. What if it's not clear how often the data will be updated, and whether you want the GUI to update automatically when it does?

The observer pattern solves this problem by using two interfaces, Observer and Observable. As the name suggests, the Observer "watches'' the Observable to see whether it changes.

Note 

Keeping with the theme of human senses, an Observer is sometimes called a Listener, but for this chapter we stick with the former name.

In its most basic implementation, the Observable can add Observers. Observable then is responsible for notifying them if anything about its state has changed, and the Observer is responsible for reacting to the change. In this example, our data is the Observable and the GUI components are the Observers. If the data changes, those changes will automatically be reflected in any GUI component that is an Observer of the data. Figure 4-6 demonstrates the observer pattern.


Figure 4-6

Widgets

Continuing with the previous example, you'll use the observer pattern to handle displaying price information for some of the instruments within graphical elements on a Web page. First, you'll need to define some simple graphical components. These components will be basic HTML table structures whose functionality will be contained within an object. Such components often go under the all-purpose term of widgets.

The widgets need to display the same instrument information. For this example, that information is just the instrument's and price.

Designing the Widgets

A graphical widget will have two responsibilities. It will need to draw its own HTML so that it can be seen on a Web page, and it will need to update the data it displays. You may have noticed (from Figure 4-6) that an update method is defined in the Observer interface.

Each widget is an Observer. The item being observed is the data representing the instrument name and price information. The data source is Observable.

All widgets, then, should implement the Observer interface. In addition, each one should descend from an abstract class that defines some shared functionality between widget objects. This is an example of using interfaces and abstract classes together. Because the update() method is the same for all widgets, it can be implemented in the abstract class. The class diagram is shown in Figure 4-7.


Figure 4-7

Now all concrete implementations of widgets will descend from the AbstractWidget class. The AbstractWidget class in turn implements the Observer interface. Notice the update() method in the class diagram for AbstractWidget is not shown in italics. This means that the method is actually implemented at that point. The # symbol indicates that the internalData property is protected. That means subclasses of AbstractWidget have access to it. If it were private, subclasses would not be able to access it.

Take a look at the following Widget code, in a file called abstract_widget.php:

   <?php    interface Observer {      public function update();    }    abstract class Widget implements Observer {      protected $internalData = array();      abstract public function draw();      public function update(Observable $subject) {             $this->internalData = $subject->getData();      }    }    class BasicWidget extends Widget {      function __construct() {      }      public function draw() {             $html = "<table border=1 width=130>";             $html .= "<tr><td colspan=3 bgcolor=#cccccc>                            <b>Instrument Info<b></td></tr>";             $numRecords = count($this->internalData[0]);             for($i = 0; $i < $numRecords; $i++) {                    $instms = $this->internalData[0];                    $prices = $this->internalData[1];                    $years = $this->internalData[2];                    $html .= "<tr><td>$instms[$i]</td><td> $prices[$i]</td>                              <td>$years[$i]</td></tr>";                    }            $html .= "</table><br>";            echo $html;      }    }    class FancyWidget extends Widget {      function __construct() {      }      public function draw() {             $html =             "<table border=0 cellpadding=5 width=270 bgcolor=#6699BB>                     <tr><td colspan=3 bgcolor=#cccccc>                    <b><span class=blue>Our Latest Prices<span><b>                    </td></tr>                    <tr><td><b>instrument</b></td>                    <td><b>price</b></td><td><b>date issued</b>                    </td></tr>";            $numRecords = count($this->internalData[0]);            for($i = 0; $i < $numRecords; $i++) {                   $instms = $this->internalData[0];                   $prices = $this->internalData[1];                   $years = $this->internalData[2];                    $html .=                    "<tr><td>$instms[$i]</td><td>                            $prices[$i]</td><td>$years[$i]                            </td></tr>";                    }             $html .= "</table><br>";             echo $html;      }    }    ?> 

There are two concrete Widget implementations, FancyWidget and BasicWidget. Both implement the draw() method required from the abstract parent class they extend, yet the implementations are different. Both also inherit the update() method from the parent class.

You might be wondering about the benefit of using the Observer interface when you could just put the single method in the subclasses anyway. Earlier PHP versions provided no direct way to ensure that a method parameter was of a certain type. A new feature in PHP5 called class type hints serves as a way of guaranteeing that the correct type of object is passed as an argument. In the following function declaration, the reference passed as an argument must be of type Observer or you get an error.

   public function addObserver(Observer $observer) { 

If a particular method takes an Observer as an argument as is the case with the Observable addObserver() method you know it's safe to pass a Widget to it. That's because all Widget objects are of type Observer. All widgets descend from the AbstractWidget, which implements Observer, thus the widgets themselves are also that type.

Another question arises: Why not just require the method addObserver() to take a Widget? That way, you could also do away with the Observer interface. Suppose, however, that you wanted to create another type of Observer that wasn't a Widget. Then the class type hint would prevent you from passing any other type of object to the addObserver() method.

The DataSource

The DataSource object encapsulates the name, price, and date of issue for a group of musical instruments. It's also our Observable object. The key methods in Observer are addObserver() and notifyObservers(). Any Observer (in this case, any Widget) that needs to "watch'' the DataSource can be added to it using addObserver(). The following code comprises observable.php:

   <?php    abstract class Observable {      private $observers = array();      public function addObserver(Observer $observer) {             array_push($this->observers, $observer);      }      public function notifyObservers() {             for ($i = 0; $i < count($this->observers); $i++) {                     $widget = $this->observers[$i];                     $widget->update($this);             }         }    }    class DataSource extends Observable {      private $names;      private $prices;      private $years;      function __construct() {             $this->names = array();             $this->prices = array();             $this->years = array();      }      public function addRecord($name, $price, $year) {             array_push($this->names, $name);             array_push($this->prices, $price);             array_push($this->years, $year);             $this->notifyObservers();      }      public function getData() {             return array($this->names, $this->prices, $this->years);      }    }    ?> 

The addRecord() method lets you add a new instrument to the internal storage of the DataSource object. Notice though, that the addRecord() method does one more thing.

   $this->notifyObservers(); 

Any time the DataSource object has its internal data altered, it notifies all its observers. Observers are added using the previously mentioned addObserver() method. After an observer is added, it's stored in the internal $observers array. When notifyObservers() is called, the method iterates through the $observers array, calling the update() method for each Observer stored in the array.

The sole parameter of the update method is a copy of the Observable object itself:

   $widget->update($this); 

This allows the Widget (the Observer) to get a copy of the most current state of the DataSource (the Observable). Note that the Widget is passed by value a copy of DataSource rather than the actual reference. This way, the DataSource's internal information is not shared.

Connecting Observer and Observable

Now that you've done all the hard work up front, the payoff is an easy-to-use and flexible system for connecting an Observer Widget to the Observable DataSource. Take a look at the following example, in a file called widget.php:

   <?php    require_once("observable.php");    require_once("abstract_widget.php");    $dat = new DataSource();    $widgetA = new BasicWidget();    $widgetB = new FancyWidget();    $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);    $widgetA->draw();    $widgetB->draw();    ?> 

All you have to do is create your DataSource and Widget objects. Then have the Widget objects added as observers of the DataSource. Now you're free to add as many records as needed to the DataSource object, not concerning yourself about updating the widgets it all happens automatically. Finally, when you call draw() on the widgets, they will display themselves with the correct data.

Note 

If you were designing a desktop application, you could include a redraw() function in the update() method of the Observer. This way you wouldn't need to call draw() explicitly, but instead have the component redraw itself automatically in response to the DataSource 's being updated. In a purely server-side Web application, there's no way to redraw a component unless you reload the page.

Another cool feature of the pattern is that you can define multiple DataSources. You just tell the DataSource which widget you want listening to it, and you can reuse the same widget for different DataSources. Figure 4-8 shows the result of running the previous code.


Figure 4-8

Considerations

The observer pattern is useful when you have a source of data that you would like to connect to different representations. In this chapter we've used a simple DataSource object, but you can create more complex data sources, such as ones that retrieve data from a database or an XML file. You could design a Widget that could observe either an XML DataSource or a DB DataSource. As long as the DataSource object had the same interface, it wouldn't matter how it retrieved its data.

The Widgets in this example are coded to display a table with three columns, but they could be more flexible. If you want to get creative, try redesigning them so that they can display an arbitrary number of columns. You could also try using a little helper object instead of the arrays to transfer the information between Observable and Observer.



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