Паттерн Наблюдатель (Observer) в PHP
Метки: php, паттерны, observer
Паттерн Спостерігач (Observer) в PHP
Observer pattern in PHP
| ← Работа с форматами сжатия RAR, LZF и BZ2 в PHP | Определение и использование собственных событий в JavaScript → |
Совсем недавно я просто влюбился в шаблон проектирования observer. Вы можете с легкостью построить целое приложение, используя невероятную мощь программирования управлений событиями. Что именно представляет из себя паттерн наблюдатель? Вся теория доступна на Википедии. Говоря простым языком, у нас есть объект, за которым мы хотим мониторить (наблюдать) на предмет каких-либо изменений. Большую часть времени этот объект просто освобождает события и мы хотим прослушивать их. Мы можем иметь много объектов прослушивающих один или много других объектов, проще говоря, это может быть отношение многие ко многим. Классический пример в мире баз данных это триггеры. Например, когда таблица была обновлена, прослушивающий триггер освобождается. В оконных приложениях, когда пользователь нажимает на кнопку, события освобождается и прослушивающие уведомляются для управления событием. В следующем примере мы имеем объект валидатор, который проверяет валидность e-mail адреса, освобождающий два типа событий, которые мы хотели бы прослушивать. Эти типы, ясное дело, валидный / не валидный e-mail адрес. Мы также объявим двух слушателей, по одному на каждое из типов событий, слушающих валидатор. Первый будет просто некоторым логгером ошибок, записывать неверные попытки ввода, и второй будет записывать корректный e-mail адрес в базу данных или другое место. Мы начнем с описания нашего интерфейса для объекта наблюдения. Мы просто хотим добавить наблюдателей и освободить события. Также мы хотим иметь возможность прослушивать только определенные типы событий, значит, у нас нет необходимости оповещать наблюдателей не слушать только что освободившееся событие.
Убрать подсветку кода
Значит, любой объект, который мы хотим наблюдать, должен описывать интерфейс IObservable. Давайте опишем, как должен выглядеть наблюдатель. По простому, наблюдателю необходимо только знать, что объект освободил событие и тип этого события, значит, оно может быть обработано.
Убрать подсветку кода
Сейчас мы сделаем наш e-mail валидатор, который описывает интерфейс IObservable и определяет два типа событий.
Убрать подсветку кода
Метод, который выполняет всю работу – это validate(). Он просто проверяет полученный e-mail адрес и освобождает валидное или невалидное событие.
Это все, об остальном заботятся наблюдатели. Давайте сделаем двух наблюдателей, каждый из которых будут слушать различные типы событий. Это наблюдатель ErrorLogger, который будет оповещен каждый раз, когда проверяемый e-mail не валидный. Он может писать что-то в лог или просто предупреждать пользователя.
Убрать подсветку кода
Просто, не правда ли. ErrorLogger только описывает метод notify() и проверяет или исходное событие равно EmailValidator и тип этого события EVENT_EMAIL_INVALID, а затем пишет сообщение об ошибке. Наблюдатель для EVENT_EMAIL_VALID смотрится очень схоже.
Убрать подсветку кода
Это оно. Теперь нам всего лишь необходимо создать наш объект EmailValidator, добавить некоторых наблюдателей и дать им несколько e-mail адресов на проверку.
Убрать подсветку кода
Запустим эти примеры, должен получиться примерно такой результат:
Убрать подсветку кода
Это был лишь простой пример демонстрирующий мощь паттерна наблюдатель. Я уверен, что вы найдете множество применений для него.
Оригинал: Observer pattern in PHP
Убрать подсветку кода
1 2 3 4 5 6 | interface IObservable { public function addObserver( IObserver $objObserver, $strEventType ); public function fireEvent( $strEventType ); } |
Значит, любой объект, который мы хотим наблюдать, должен описывать интерфейс IObservable. Давайте опишем, как должен выглядеть наблюдатель. По простому, наблюдателю необходимо только знать, что объект освободил событие и тип этого события, значит, оно может быть обработано.
Убрать подсветку кода
1 2 3 4 5 | interface IObserver { public function notify( IObservable $objSource, $objArguments ); } |
Сейчас мы сделаем наш e-mail валидатор, который описывает интерфейс IObservable и определяет два типа событий.
Убрать подсветку кода
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | class EmailValidator implements IObservable { const EVENT_EMAIL_VALID = 1; const EVENT_EMAIL_INVALID = 2; protected $strEmailAddress; protected $aryObserversArray; public function __construct( $strEmailAddress ) { $this->strEmailAddress = $strEmailAddress; $this->aryObserversArray = array( array() ); } public function setEmailAddress( $strEmailAddress ) { $this->strEmailAddress = $strEmailAddress; } public function getEmailAddress() { return $this->strEmailAddress; } public function validate() { if( preg_match( '/^[a-zA-Z][\w\.-]*[a-zA-Z0-9]@'. '[a-zA-Z0-9][\w\.-]*[a-zA-Z0-9]\.[a-zA-Z][a-zA-Z\.]*[a-zA-Z]$/', $this->strEmailAddress ) ) { $this->fireEvent( EmailValidator::EVENT_EMAIL_VALID ); } else { $this->fireEvent( EmailValidator::EVENT_EMAIL_INVALID ); } } public function addObserver( IObserver $objObserver, $strEventType ) { $this->aryObserversArray[$strEventType][] = $objObserver; } public function fireEvent( $strEventType ) { if( is_array( $this->aryObserversArray[$strEventType] ) ) { foreach ( $this->aryObserversArray[$strEventType] as $objObserver ) { $objObserver->notify( $this, $strEventType ); } } } } |
Метод, который выполняет всю работу – это validate(). Он просто проверяет полученный e-mail адрес и освобождает валидное или невалидное событие.
Это все, об остальном заботятся наблюдатели. Давайте сделаем двух наблюдателей, каждый из которых будут слушать различные типы событий. Это наблюдатель ErrorLogger, который будет оповещен каждый раз, когда проверяемый e-mail не валидный. Он может писать что-то в лог или просто предупреждать пользователя.
Убрать подсветку кода
1 2 3 4 5 6 7 8 9 10 11 12 | class ErrorLogger implements IObserver { public function notify( IObservable $objSource, $strEventType ) { if( $strEventType == EmailValidator::EVENT_EMAIL_INVALID && $objSource instanceof EmailValidator ) { printf( 'Ошибка: %s невалидный email адрес.', $objSource->getEmailAddress() ); } } } |
Просто, не правда ли. ErrorLogger только описывает метод notify() и проверяет или исходное событие равно EmailValidator и тип этого события EVENT_EMAIL_INVALID, а затем пишет сообщение об ошибке. Наблюдатель для EVENT_EMAIL_VALID смотрится очень схоже.
Убрать подсветку кода
1 2 3 4 5 6 7 8 9 10 11 12 | class DatabaseWriter implements IObserver { public function notify( IObservable $objSource, $strEventType ) { if( $strEventType == EmailValidator::EVENT_EMAIL_VALID && $objSource instanceof EmailValidator ) { printf( 'Email адрес %s валидный и был записан в базу данных.', $objSource->getEmailAddress() ); } } } |
Это оно. Теперь нам всего лишь необходимо создать наш объект EmailValidator, добавить некоторых наблюдателей и дать им несколько e-mail адресов на проверку.
Убрать подсветку кода
1 2 3 4 5 6 7 8 | $objValidator = new EmailValidator( 'valid@email.com' ); $objValidator->addObserver( new ErrorLogger(), EmailValidator::EVENT_EMAIL_INVALID ); $objValidator->addObserver( new DatabaseWriter(), EmailValidator::EVENT_EMAIL_VALID ); $objValidator->validate(); $objValidator->setEmailAddress( 'not_a_valid_address' ); $objValidator->validate(); |
Запустим эти примеры, должен получиться примерно такой результат:
Убрать подсветку кода
1 2 | Ошибка: not_a_valid_address невалидный email адрес.
Email адрес valid@email.com валидный и был записан в базу данных |
Это был лишь простой пример демонстрирующий мощь паттерна наблюдатель. Я уверен, что вы найдете множество применений для него.
Оригинал: Observer pattern in PHP
Рейтинг:




<< Вы можете поставить оценку этой статьеПодобные статьи:
Паттерн кэширования для моделей
Работа с форматами сжатия RAR, LZF и BZ2 в PHP
MVC в небольших web-приложениях
Обфускаторы кода для PHP
Полнотекстовый поиск с Xapian и PHP
Обсуждение статьи:
Сергей [2009-08-27]
Небольшая ошибка в коде. Листинг 2, строка 4. Указание типа для второго аргумента не нужно.
HeeL [2009-08-27]
Спасибо, исправлено
Vik [2009-09-08]
не пойму, чем это круче, вот такого:
function validemail($email) {
return preg_match(регулярка);
}
if (!validemail('bill@gates.com')) die('Err');
Зачем городить патерны и все такое? Объясните плз.
Алесь [2009-10-20]
Очень люблю ООП. Но не могу не спросить вместе с Vik, А зачем?
KelTanas [2009-11-12]
Несомненно, в более сложной системе преимущества будут на лицо.
В конкретной задаче, ИМХО, реализация конструктора в EmailValidator лишняя. Для добавления адресов логичнее пользоваться только setEmailAddress().
Если метод addObserver() будет возвращать $this, можно будет добавлять события с перегрузкой.
cheburek [2010-04-13]
ИМХО обсервер для PHP совершенно чуждая идея. Как и большинство патернов. А действительно полезный только MVC.
Нет ничего хуже бездумного поклонения чужим идеям.
oleg [2010-05-14]
зачем, зачем... Чтобы изучить паттерн и не более!
house M. D. [2010-05-27]
addObserver есть, а deleteObserver нет!
и вообще объект хранящий "обсерверы" должен быть одиночкой
leonbobster [2010-09-14]
to cheburek вы бы посмотрели в исходники orm Doctrine хотя бы, или фреймворки PHP
partyzan [2010-11-12]
to всем непонятливым.
Если ,вы господа, пишите хоумпейджи и прочие мелкие проекты, то безусловно паттерны и вообще весь ООД - это только непонятный монстр усложняющий код. Но в серьёзных проектах, где работают больше трёх, да ещё в разных странах часто возникает проблема понимания кода. Напишет, к примеру, кодер из Гонконга валидатор мыла как-то по своемому - по-китайски, а кодеру из Далласа станет задача код китайца развить и доработать (например добавить валидацию имени и телефона). И начинается многократная переделка чужого кода, бесконечный дебаггинг и т.д. Развивать код написанный не на паттернах сложно. Паттрены - это просто некие стандарты которые дают определённую гибкость в написании и дописывании кода.
Кроме этого хочу напомнить что не всегда код доступен. Для ПХПешников это как бы не проблема - редко кто код свой компилит гуардом иди ещё каким-нибудь способом. Но если вы пишете на Джава или Си++ и вам выдают готовые модули в формате dll или class и только описание интерфейсов к ним, то вот тут уже не залезешь и не поправишь как тебе надо. На выручку приходят паттерны типа адаптер, мост и т.п.
Знание ООП без понимания ООД это всё равно что иметь права, но при этом не ездить за рулём.
HeeL [2010-11-12]
partyzan, красиво объяснил, лучше и не скажешь
partyzan [2010-11-12]
Хочу дополнить, что без ООД (объектно ориентированный дизайн, если кто не в курсе как это расшифровывается) некоторые вещи ООП не имеют практического применения и порой непонятны их назначение. Например интерфейсы и абстрактные классы. У меня в отделе джуниоры часто задают вопросы: а нафига эти штуковины? Какой толк от интерфейса если можно написать \"понятный\" класс и с ним работать. И нафик нужен абстрактник если от него объект не породишь? И приходилось раньше на пальцах показывать его применение. Сейчас я даю документацию по ООД и \"сиди учи\". В ООД без интерфейсов и абстрактных классов уже сложновато (хотя можно и обойтись обычными классами - но это уже будет извращение)
Kate [2010-11-12]
partyzan, подписываюсь под каждым твоим словом
PVP [2011-03-12]
to partyzan
Какой смысл людям пишущим такое
if (!validemail('bill@gates.com')) die('Err');
вообще что то объяснять? :)
Он даже не понимает, что это пример использования наблюдателя, а не реально валидатор. Тот кто пишет подобное... Точнее у кого вообще в голове возникают такие строки - безнадежен. Абсолютно.
Чупакабра [2011-03-22]
Я бы еще абстрактный класс с методами notify и fireEvent, ну и фабричку небольшую туда всунуть можна чтобы в зависимости от ивента оповещало нужный класс-наблюдатель.
Хотя для демонстрации именно паттерна наблюдатель статья отличная, сенкс.
Medionis [2011-04-18]
Я не знаю, для чего нужен паттерн Наблюдатель.
После нескольких прочтений этой статьи я так этого и не узнал.
Эта статья написана для тех, кому она не нужна?
MaksFM [2011-06-11]
Последовательность вывода сообщений должна быть обратная.
Email адрес valid@email.com валидный и был записан в базу данных
Ошибка: not_a_valid_address невалидный email адрес.
Max [2011-06-17]
thanks
Dr0ID [2011-11-14]
Года 2 назад я тоже сказал "зачем городить огород?", а сейчас сразу понимаю в каких реальных кейсах из моих проектов это можно использовать. Как правило проблема понимания статей (как про ООП, так и про паттерны) в том, что рассматриваются тривиальные и надуманные кейсы и человек, в силу небольшого опыта, не может понять где, как и зачем это использовать.