Шаблон Observer на PHP



















Observer(Наблюдатель) - является поведенческим шаблоном проектирования. Является достаточно популярным шаблоном проектирования, но, при этом, очень прост в реализации. Данный шаблон предполагает зависимость между объектами "один ко многим" так, что при изменении состояния одного объекта все зависящие от него объекты уведомляются и обновляются автоматически. Таким образом, в шаблоне наблюдаются две роли: субъект и слушатель. Шаблон "Наблюдатель"определяется следующими свойствами:



  • существует, как минимум, один субъект, рассылающий сообщения;

  • имеется не менее одного получателя сообщений, причём их количество и состав могут изменяться во время работы приложения;

  • нет надобности очень сильно связывать взаимодействующие объекты, что полезно для повторного использования.


Шаблон подходит для любого сценария, в котором требуется использование push-уведомлений. Очень часто его можно заметить в системах пользовательского интерфейса.



Аналогия


Самая банальная аналогия - подписка на рассылки какого-либо сайта. Когда на сайте появляется новый материал, то всем подписчикам рассылается e-mail-уведомление о появлении нового материала, а подписчики уже принимают решение просматривать новый материал или нет.




UML-диаграмма


Типовая UML-диаграмма выглядит следующим образом:


UML-диаграмма шаблона Observer

На ней присутствуют следующие сущности:




  • IObservable(наблюдаемый) — интерфейс, определяющий методы для добавления, удаления и оповещения наблюдателей;


  • IObserver(наблюдатель) — интерфейс, с помощью которого наблюдатель получает оповещение;


  • ConcreteObservable — конкретный класс, который реализует интерфейс IObservable;


  • ConcreteObserver — конкретный класс, который реализует интерфейс IObserver.



Код шаблона


Фактически, для реализации шаблона "Наблюдатель" необходимо описать два интерфейса с необходимыми методами: IObservable и IObserver. Код интерфейсов:


interface IObservable {
/**
* Добавление нового наблюдателя
*
* @param IObserver $instance
* @return bool
*/
public function attach(IObserver $instance);

/**
* Удаление имеющегося наблюдателя
*
* @param IObserver $instance
* @return bool
*/
public function detach(IObserver $instance);

/**
* Оповещение всех наблюдателей, через вызов у него метода update
*
*/
public function notify();
}

interface IObserver {
/**
* Будет вызван у каждого наблюдателя в notify()
*
* @param IObservable $instance
* @return mixed
*/
public function update(IObservable $instance);
}

Реализация методов, указанных в интерфейсах ложится на классы, где эти самые интерфейсы и будут использованы. Например, используя данный шаблон проектирования, мы будем следить за погодой. Допустим, у нас есть класс, который генерирует погоду и несколько слушателей, которые подписаны на обновление прогноза погоды. В коде это выглядит следующим образом:


class WeatherGenerator implements IObservable {
/**
* Массив наблюдателей
*
* @var IObserver[]
*/
private $observers = array();

/**
* Температура
*
* @var float
*/
private $temperature;

/**
* Давление
*
* @var float
*/
private $pressure;

public function attach(IObserver $instance) {
foreach ($this->observers as $observer) {
if ($instance === $observer) {
return false;
}
}
$this->observers[] = $instance;
return true;
}

public function detach(IObserver $instance) {
foreach ($this->observers as $key => $observer) {
if ($instance === $observer) {
unset($this->observers[$key]);
return true;
}
}

return false;
}

public function notify() {
foreach ($this->observers as $observer) {
$observer->update($this);
}
}

public function setParams($aTemperature, $aPressure) {
$this->temperature = $aTemperature;
$this->pressure = $aPressure;
$this->notify();
}

public function getTemperature() {
return $this->temperature;
}

public function getPressure() {
return $this->pressure;
}
}

class WeatherListener1 implements IObserver {
private $currentTemperature;
private $currentPressure;

public function update(IObservable $instance) {
$this->currentTemperature = $instance->getTemperature();
$this->currentPressure = $instance->getPressure();
$this->display();
}

public function display() {
echo '<b>WeatherListener1</b> Текущая температура: '.$this->currentTemperature.'; Текущее давление: '.$this->currentPressure.'<br/>';
}
}

class WeatherListener2 implements IObserver {
private $currentTemperature;
private $currentPressure;

public function update(IObservable $instance) {
$this->currentTemperature = $instance->getTemperature();
$this->currentPressure = $instance->getPressure();
$this->display();
}

public function display() {
echo '<b>WeatherListener2</b> Текущая температура: '.$this->currentTemperature.'; Текущее давление: '.$this->currentPressure.'<br/>';
}
}

Ну и добавим код приложения:


$wl1 = new WeatherListener1();
$wl2 = new WeatherListener2();
$weatherGenerator = new WeatherGenerator();
$weatherGenerator->attach($wl1);
$weatherGenerator->attach($wl2);
$weatherGenerator->setParams(14, 770);
$weatherGenerator->setParams(16, 750);
$weatherGenerator->detach($wl1);
$weatherGenerator->setParams(17, 745);


Результат работы скрипта:


WeatherListener1 Текущая температура: 14; Текущее давление: 770
WeatherListener2 Текущая температура: 14; Текущее давление: 770
WeatherListener1 Текущая температура: 16; Текущее давление: 750
WeatherListener2 Текущая температура: 16; Текущее давление: 750
WeatherListener2 Текущая температура: 17; Текущее давление: 745


Что в итоге? У нас есть два объекта классов, реализующих интерфейс слушателя(WeatherListener1, WeatherListener2) и один объект класса, генерирующего погоду, который также может оповещать об изменениях погоды(WeatherGenerator). Изначально оба слушателя оповещаются об изменениях погоды, но после второго изменения один из слушателей перестает оповещаться о погоде. Реализация шаблона "Наблюдатель" продемонстрирована в действии.



Как улучшить реализацию?


При анализе шаблона можно сделать вывод, что реализация интерфейса Observable будет всегда выглядеть одинаково. В таком случае целесообразно этот код вынести в отдельный класс и от него наследоваться. Такой подход имеет один небольшой минус - создается ненужная цепочка наследования. Но в php 5.4.0 появилось идеальное, более прогрессивное решение для таких случаев - трейты. Можно и нужно создать трейт, который будет реализовывать интерфейс Observable и впоследствии использовать этот трейт в классе, за которым нужно следить. Код трейта в таком случае будет таким:


trait TObservable {
/**
* Массив наблюдателей
*
* @var TObserver[]
*/
private $observers = array();

public function attach(IObserver $instance) {
foreach ($this->observers as $observer) {
if ($instance === $observer) {
return false;
}
}
$this->observers[] = $instance;
return true;
}

public function detach(IObserver $instance) {
foreach ($this->observers as $key => $observer) {
if ($instance === $observer) {
unset($this->observers[$key]);
return true;
}
}
return false;
}

public function notify() {
foreach ($this->observers as $observer) {
$observer->update($this);
}
}
}

А использовать его следует таким образом:


class WeatherGenerator implements IObservable {
use TObservable;
...
}

В таком случае, UML-диаграмма реализации будет следующей:


Модифицированная UML-диаграмма шаблона Observer
4313   0  

Comments

    Ничего не найдено.