Статьи

Понимание паттерна наблюдателя

Недавно меня попросили интегрировать стороннее программное обеспечение для форумов в существующий веб-портал. Проблема заключалась в том, что оба приложения имели собственный механизм аутентификации. С точки зрения пользователя, было бы идеально для пользователей войти на портал без необходимости входа на форум отдельно. И в этом случае я не хотел изменять код форума без необходимости, поскольку это создало бы кошмар обслуживания — мне пришлось бы объединять любые последующие обновления и исправления ошибок от поставщика и быть осторожным, чтобы не перезаписать мои собственные изменения. Вот где мне пригодился шаблон наблюдателя.

В этой статье я покажу вам, как реализовать шаблон Observer. Вы узнаете, как различные классы в шаблоне относятся друг к другу как субъект и наблюдатели, как субъект уведомляет наблюдателей об изменении своего состояния и как определить сценарии, в которых было бы целесообразно использовать шаблон наблюдателя в своем собственном коде. ,

Образец Наблюдателя

Шаблон наблюдателя (также известный как шаблон публикации-подписки) является шаблоном поведенческого проектирования, который определяет отношение «один ко многим» между объектами, так что, когда один объект меняет свое состояние, все зависимые объекты уведомляются и обновляются автоматически. В моем случае я использовал этот шаблон для связи механизма аутентификации портала с программным обеспечением форума. При входе на портал также автоматически запускается вход пользователя на форум.

Объект, имеющий отношение один ко многим с другими объектами, которые заинтересованы в его состоянии, называется субъектом или издателем . Его зависимые объекты называются наблюдателями или подписчиками . Наблюдатели уведомляются всякий раз, когда меняется состояние субъекта, и могут действовать соответствующим образом. Субъект может иметь любое количество зависимых наблюдателей, которых он уведомляет, и любое количество наблюдателей может подписаться на субъект для получения таких уведомлений.

Класс наблюдателя

Класс Observer предоставляет метод update() который субъект будет вызывать для уведомления об изменении состояния субъекта. В этом примере я определил метод update() как конкретный метод. Если вам нравится, вы можете определить метод здесь как abstract а затем предоставить конкретную реализацию для него в подклассе вашего наблюдателя.

 <?php abstract class Observer { public function __construct($subject = null) { if (is_object($subject) && $subject instanceof Subject) { $subject->attach($this); } } public function update($subject) { // looks for an observer method with the state name if (method_exists($this, $subject->getState())) { call_user_func_array(array($this, $subject->getState()), array($subject)); } } } 

Метод __construct() принимает экземпляр наблюдаемого субъекта и присоединяется к субъекту — к которому я скоро вернусь. Метод update() извлекает текущее состояние субъекта и использует его для вызова метода наблюдателя с подклассами, имя которого совпадает с именем состояния.

Поэтому в моем конкретном случае мне нужно было сделать существующий класс портала Auth наблюдаемой темой и создать конкретный подкласс наблюдателя, чтобы подключиться к коду аутентификации форума. Мой подкласс также должен был реализовать методы, использующие состояния субъекта.

Предметный класс

Класс Subject также является абстрактным классом и определяет четыре основных метода: attach() , detach() , setState() и setState() notify() . Для удобства я также добавил getState() и getObservers() .

 <?php abstract class Subject { protected $observers; protected $state; public function __construct() { $this->observers = array(); $this->state = null; } public function attach(Observer $observer) { $i = array_search($observer, $this->observers); if ($i === false) { $this->observers[] = $observer; } } public function detach(Observer $observer) { if (!empty($this->observers)) { $i = array_search($observer, $this->observers); if ($i !== false) { unset($this->observers[$i]); } } } public function getState() { return $this->state; } public function setState($state) { $this->state = $state; $this->notify(); } public function notify() { if (!empty($this->observers)) { foreach ($this->observers as $observer) { $observer->update($this); } } } public function getObservers() { return $this->observers; } } не <?php abstract class Subject { protected $observers; protected $state; public function __construct() { $this->observers = array(); $this->state = null; } public function attach(Observer $observer) { $i = array_search($observer, $this->observers); if ($i === false) { $this->observers[] = $observer; } } public function detach(Observer $observer) { if (!empty($this->observers)) { $i = array_search($observer, $this->observers); if ($i !== false) { unset($this->observers[$i]); } } } public function getState() { return $this->state; } public function setState($state) { $this->state = $state; $this->notify(); } public function notify() { if (!empty($this->observers)) { foreach ($this->observers as $observer) { $observer->update($this); } } } public function getObservers() { return $this->observers; } } 

Метод attach() подписывает наблюдателя на субъект, так что любые изменения в состоянии могут быть переданы ему. Метод detach() отписывает наблюдателя от субъекта, так что он больше не наблюдает за субъектом для изменений состояния.

Метод setState() устанавливает текущее состояние субъекта и вызывает notify() для обновления наблюдателей, т.е. публикует уведомления для каждого наблюдателя. Метод notify() в свою очередь, обновляет каждый из подписанных объектов, просматривая внутренний список и вызывая метод update() каждого члена.

getState() и getObservers() — это просто вспомогательные функции, которые возвращают текущее состояние субъекта и список наблюдателей.

Соблюдайте внимательно … Собираем вместе

Теперь, имея абстрактные базовые классы для наблюдателя и субъекта, я смог интегрировать программное обеспечение форума в существующий веб-портал. Мне нужно было сделать класс Auth портала Auth субъектом и установить его наблюдаемое состояние, когда пользователь входит или выходит из портала.

 <?php class Auth extends Subject { function login() { // existing code to perform login authentication // ... // signal any observers that the user has logged in $this->setState("login"); } function logout() { // existing code to perform some operation when user logs out // eg destroy session, etc... // signal any observers that the user has logged out $this->setState("logout"); } } из <?php class Auth extends Subject { function login() { // existing code to perform login authentication // ... // signal any observers that the user has logged in $this->setState("login"); } function logout() { // existing code to perform some operation when user logs out // eg destroy session, etc... // signal any observers that the user has logged out $this->setState("logout"); } } 

Я расширил класс Subject так что Auth можно наблюдать, а затем добавил вызов setState() в методах login() и logout() .

Чтобы создать подкласс Observer , я создал класс Auth_ForumHook который отвечал за вызов функций входа и выхода из API на форуме.

 <?php class Auth_ForumHook extends Observer { function login($subject) { // call the forum's API functions to log the user in // ... } function logout($subject) { // call the forum's API functions to log the user out // ... } } 

В другом месте кодовой базы, где Auth_ForumHook, класс Auth портала, я прикрепил экземпляр Auth_ForumHook, к нему, чтобы наблюдатель был уведомлен о любых изменениях состояния в Auth .

 <?php $auth = new Auth(); // attach an observer $auth->attach(new Auth_ForumHook()); ... 

Это было пределом моих дополнительных требований к кодированию — кроме подготовки абстрактных классов Observer и Subject . Любое изменение состояния, вызванное методами Auth login() и logout() уведомляет наблюдателя Auth_ForumHook и автоматически регистрирует пользователя на форуме или вне его.

Чтобы добавить новых наблюдателей, скажем, средство отслеживания входа для записи, когда пользователь входит или выходит из портала, нужно просто предоставить конкретный класс Observer и присоединить его к субъекту Auth без необходимости дальнейшей модификации существующего объекта Auth . методы login() и logout() .

Резюме

Шаблон наблюдателя — это подходящий шаблон проектирования, который можно применять в любой ситуации, когда у вас есть несколько объектов, которые зависят от другого объекта и должны выполнять действие при изменении состояния этого объекта или когда объект должен уведомлять других, не зная, кто они. Есть или сколько их есть.

В этой статье я показал вам базовый шаблон Subject-Observer и предоставил конкретные примеры того, как вы можете использовать этот поведенческий шаблон, чтобы легко расширить функциональность существующего класса без тесной связи каких-либо новых требований. Этот шаблон позволяет достичь более высокого уровня согласованности между связанными и зависимыми объектами без ущерба для повторного использования кода.

Изображение через JPF / Shutterstock