Знаниями нужно делится...

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

Назад к списку | Просмотров: 4991

Одним из принципов разработке программ с объектно-ориентированным подходом (да и не только) является принцип слабой связи. Задача, которой организовать код программы таким образом, что бы различные компоненты и модули были, как можно меньше связаны друг с другом.

Как правило, жесткие связи образуются при реализации функций обратного вызова, когда требуется сообщить из одной части в программы в другую о некотором событии. Для решения задачи обхода обратного вызова прекрасно подходит шаблон проектирования Observer(наблюдатель). Он позволят разгрузить код и ослабить связи между различными модулями системы. Шаблон состоит из трех основных компонентов – слушателя событий, подписчика на события и объекта события.

Слушатель событий в моей реализацией может быть любой класс реализующий интерфейс IVsEventHandler. Метод handler будет принимать объекты-события типа VsEvent, далее в методе в зависимости от типа события можно реализовать конкретную логику.

interface IVsEventHandler {
    function handler(VsEvent $event);
}

Объект события представляет структуру данных с информацией о типе события, месте где произошло событие и дополнительных параметров.

class VsEvent {
    
    /**
     * Тип события перед запуском основного приложения 
     */
    const TYPE_APP_BEFORE_START = 'typeAppBeforeStart';
    
    /**
     *  Тип события  после запуска основного приложения 
     */
    const TYPE_APP_AFTER_START = 'typeAppAftereStart';
 
    
    /**
     * Тип события
     * @var string 
     */
    private $type;
    
    /**
     *  Дополнительные параметры
     * @var mixed 
     */
    private $source;
    
    /**
     * Ссылка на объект где произошло событие
     * @var object 
     */
    private $target;
    
    public function __construct($type, $target = NULL, $source = NULL) {
        $this->type = $type; 
        $this->target = $target;
        $this->source = $source;
    }
    
    /**
     * Получить тип события
     * @return string 
     */
    public function getType() {
        return $this->type;
    }
    
    /**
     * Получить дополнительные параметры
     * @return mixed 
     */
    public function getSource() {
        return $this->source;
    }
    
    /**
     * Получить ссылку на объект где произошло событие
     * @return object|null 
     */
    public function getTarget() {
        return $this->target;
    }
    
    /**
     * Установить объект где произошло событие
     * @param object $target 
     */
    public function setTarget($target) {
        $this->target = $target;
    }
    
    /**
     * 
     * @return string 
     */
    public function __toString() {
        return get_class($this).' type:'.$this->getType().'; target:'.get_class($this->getTarget()).'; source:'.sizeof($this->getSource()).'';
    }
} 

В зависимости от конкретной реализации, вы можете изменять объект VsEvent, добавляя различные методы и свойства. Подписчик на события представляет собой хранилище объектов реализующих интерфейс IVsEventHandler и имеющий по необходимости передать им объект произошедшего события. В своей реализации подписчика VsObserver я решил применить еще и паттерн singleton (одиночка) , что бы сделать объект глобальным и доступным из любого места кода.

/**
 * Description of VsObserver
 *
 * @author v.raskin
 */
class VsObserver {
    
    
    /**
     *
     * @var VsObserver 
     */
    private static $inst = NULL;
    
    /**
     *
     * @var type 
     */
    private $observers = array();


    final private function __construct() { }
    
    /**
     *
     * @return VsObserver 
     */
    private static function getInstance() {
        if(is_null(self::$inst)) {
            self::$inst = new self();
        }
        return self::$inst;
    }
    
    /**
     * Установить слушателя
     * @param string $type
     * @param IVsEventHandler $observer 
     */
    public static function addObserver($type, IVsEventHandler $observer) {
        $instance = self::getInstance();
        if(isset($instance->observers[$type])) {
            $instance->observers[$type] = array();
        }
        $instance->observers[$type][] = $observer;
    }
    
    /**
     * Удалить слушателя
     * @param string $type
     * @param IVsEventHandler $observer 
     */
    public static function removeObserver($type, IVsEventHandler $observer) {
        $instance = self::getInstance();
        if(isset($instance->observers[$type])) {
            $data = array();
            foreach($instance->observers[$type] as $obs) {
                if($observer == $obs) {
                    continue;
                }
                $data[] = $obs;
            }
            $instance->observers[$type] = $data;
        }
    }
    
    /**
     * Рассылка сообщений
     * @param VsEvent $event 
     */
    public static function notify(VsEvent $event) {
        $instance = self::getInstance();
        $type = $event->getType();
        if(isset($instance->observers[$type])) { 
            if(is_null($event->getTarget())) {
                $event->setTarget($instance);
            }
            foreach($instance->observers[$type] as $obs) {
                $obs->handler($event); 
            }
        }
    }
}

Ниже приведен наглядный пример кода, демонстрирующий принцип работы паттерна.

class App implements IVsEventHandler {
    
    function start() {
        VsObserver::notify(new VsEvent(VsEvent::TYPE_APP_BEFORE_START, $this, array()));
        VsObserver::notify(new VsEvent(VsEvent::TYPE_APP_AFTER_START, $this, array()));  
    }
    
    function stop() {
        VsObserver::notify(new VsEvent('appEnd', $this, array()));
    }
    
    function handler(VsEvent $event) {
        print $event;
        print '
'; } } class User { function login($username, $password) { VsObserver::notify(new VsEvent('userLogin', $this, array($username, $password))); } } $app = new App(); VsObserver::addObserver(VsEvent::TYPE_APP_BEFORE_START, $app); VsObserver::addObserver(VsEvent::TYPE_APP_AFTER_START, $app); VsObserver::addObserver('appEnd', $app); VsObserver::addObserver('userLogin', $app); $app->start(); $user = new User(); $user->login('user', 'pass'); $app->stop();
//Вывод
VsEvent type:typeAppBeforeStart; target:App; source:0
VsEvent type:typeAppAftereStart; target:App; source:0
VsEvent type:userLogin; target:User; source:2
VsEvent type:appEnd; target:App; source:0 

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


автор admin дата 14/10/2013


Оставить комментарий
9 + 0 =