Часто в вашем приложении есть данные, которые меняются со временем. Например, предположим, что есть данные интерфейса пользователя, которые необходимы для их отображения и последующего обновления при их изменении. Как вы бы справились с этим? Одним из решений может быть передача недавно обновленных данных в метод интерфейса. Проблема с этим подходом — не забывать, что каждый раз данные обновляются. Что делать, если неясно, как часто данные будут обновляться и будут ли, а вы хотите, чтобы интерфейс обновлялся автоматически.
Паттерн Наблюдатель решает эту проблему, используя два интерфейса: Observer и Observable. Исходя из названия предполагается, что Observer «наблюдает» за Observable «наблюдаемым», чтобы увидеть, изменяется ли он. Сохраняя тему человеческих чувств, Наблюдателя иногда называют Слушателем, но мы будем придерживаться прежнего названия. В своей самой базовой реализации Observable может добавлять наблюдателей. Наблюдаемый тогда отвечает для уведомления их, если что-либо в своем состоянии изменилось, а Наблюдатель отвечает за реагирование на изменения.
interface Observer { public function update(Observable $subject); } 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> <tr> <td colspan=3 bgcolor=#cccccc> <b> <span> 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; }}
Объект DataSource инкапсулирует название, цену и дату выпуска группы музыкальных инструментов. Это же объект Observable. Ключевыми методами в Observer являются addObserver() и
notifyObservers(). Любого наблюдателя (в данном случае любой виджет), которому необходимо «наблюдать» за источником данных можно добавить с помощью addObserver().
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); } }
Метод addRecord() позволяет добавить новый инструмент во внутреннюю память объекта DataSource. Однако обратите внимание, что метод addRecord() выполняет еще одну вещь:
$this->notifyObservers();
Каждый раз, когда объект DataSource изменяет свои внутренние данные, он уведомляет всех своих наблюдателей. Наблюдатели
добавленны с использованием ранее упомянутого метода addObserver(). После добавления наблюдателя он сохраняется во внутренний массив наблюдателей. Когда вызывается notifyObservers(), метод проходит через массив $observers, вызывающий метод update() для каждого Observer, хранящегося в массиве. Единственный параметр метода обновления — это копия самого объекта Observable:
$widget->update($this);
Это позволяет виджету (Наблюдателю) получать копию самого последнего состояния источника данных (Наблюдаемого). Обратите внимание, что виджет передается по значению — копии DataSource, а не фактического ссылка. Таким образом, внутренняя информация DataSource не передается.
Соединение Observer и Observable
Теперь, когда выполнена вся сложная работа, паттерн представляет собой простую в использовании и гибкую систему для
подключение виджета наблюдателя к наблюдаемому источнику данных.
$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();
Все, что вам нужно сделать, это создать объекты DataSource и Widget. Затем вы можете добавить объекты Widget в качестве наблюдателей за источником данных. Теперь вы можете добавлять столько записей, сколько необходимо в DataSource объект, не заботясь об обновлении виджетов — все происходит автоматически. Наконец, когда вы вызываете draw() для виджетов, они отобразят себя с правильными данными.
Если вы разрабатывали настольное приложение, вы могли бы включить функцию redraw() в update() объекта Observer. Таким образом, вам не нужно будет явно вызывать draw(), а вместо этого
компонент автоматически перерисовывает себя в ответ на обновление источника данных. В чисто серверных веб-приложениях, нет возможности перерисовать компонент, пока вы не перезагрузите страницу.