Паттерн Наблюдатель (Observer) в PHP

Метки: php, паттерны, observer

Паттерн Спостерігач (Observer) в PHP Паттерн Спостерігач (Observer) в PHP
Observer pattern in PHP Observer pattern in PHP

Совсем недавно я просто влюбился в шаблон проектирования observer. Вы можете с легкостью построить целое приложение, используя невероятную мощь программирования управлений событиями. Что именно представляет из себя паттерн наблюдатель? Вся теория доступна на Википедии. Говоря простым языком, у нас есть объект, за которым мы хотим мониторить (наблюдать) на предмет каких-либо изменений. Большую часть времени этот объект просто освобождает события и мы хотим прослушивать их. Мы можем иметь много объектов прослушивающих один или много других объектов, проще говоря, это может быть отношение многие ко многим. Классический пример в мире баз данных это триггеры. Например, когда таблица была обновлена, прослушивающий триггер освобождается. В оконных приложениях, когда пользователь нажимает на кнопку, события освобождается и прослушивающие уведомляются для управления событием. В следующем примере мы имеем объект валидатор, который проверяет валидность e-mail адреса, освобождающий два типа событий, которые мы хотели бы прослушивать. Эти типы, ясное дело, валидный / не валидный e-mail адрес. Мы также объявим двух слушателей, по одному на каждое из типов событий, слушающих валидатор. Первый будет просто некоторым логгером ошибок, записывать неверные попытки ввода, и второй будет записывать корректный e-mail адрес в базу данных или другое место. Мы начнем с описания нашего интерфейса для объекта наблюдения. Мы просто хотим добавить наблюдателей и освободить события. Также мы хотим иметь возможность прослушивать только определенные типы событий, значит, у нас нет необходимости оповещать наблюдателей не слушать только что освободившееся событие.
Убрать подсветку кода
1
2
3
4
5
6
<?php
interface IObservable
{
  public function addObserver( IObserver $objObserver, $strEventType );
  public function fireEvent( $strEventType );
}

Значит, любой объект, который мы хотим наблюдать, должен описывать интерфейс IObservable. Давайте опишем, как должен выглядеть наблюдатель. По простому, наблюдателю необходимо только знать, что объект освободил событие и тип этого события, значит, оно может быть обработано.
Убрать подсветку кода
1
2
3
4
5
<?php
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
<?php
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
<?php
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
<?php
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
<?php
$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

Рейтинг: 12345   << Вы можете поставить оценку этой статье


Подобные статьи:
   Паттерн кэширования для моделей
   Работа с форматами сжатия 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 нет!
и вообще объект хранящий "обсерверы" должен быть одиночкой


 

Оставить комментарий:

Имя


E-mail


Сообщение