Подтвердить что ты не робот

Как полезен SplSubject/SplObserver?

Стандартная библиотека PHP включает то, что некоторые ресурсы называют ссылочной реализацией шаблона Observer, с помощью SplSubject и SplObserver. В моей жизни я не могу понять, насколько они очень полезны без возможности передачи фактических событий или любой другой информации вместе с уведомлениями:

class MySubject implements SplSubject {
    protected $_observers = [];

    public function attach(SplObserver $observer) {
        $id = spl_object_hash($observer);
        $this->_observers[$id] = $observer;
    }

    public function detach(SplObserver $observer) {
        $id = spl_object_hash($observer);

        if (isset($this->_observers[$id])) {
            unset($this->_observers[$id]);
        }
    }

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

class MyObserver implements SplObserver {
    public function update(SplSubject $subject) {
        // something happened with $subject, but what
        // was it???
    }
}

$subject = new MySubject();
$observer = new MyObserver();

$subject->attach($observer);
$subject->notify();

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


Edit:

Вот моя самая большая проблема с интерфейсом (хотя есть и другие):

public function update(SplSubject $subject, Event $event) { /* ... */ }

... устанавливает следующую фатальную ошибку:

PHP Fatal error:  Declaration of MyObserver::update() must be compatible with SplObserver::update(SplSubject $SplSubject)

Изменить # 2:

Предоставление дополнительных параметров по умолчанию, задавая их значения по умолчанию, предотвращает фатальную ошибку и обеспечивает способ передачи контекста, что делает реализации целесообразными. Я раньше не знал об этом, так что это в значительной степени отвечает на мой вопрос. Решение состоит в том, чтобы передать ваши собственные данные о событии/сообщении и проверить его существование внутри SplObserver::update().

4b9b3361

Ответ 1

Вы можете реализовать метод обновления с дополнительным параметром и по-прежнему удовлетворять интерфейсу SplSubject.

class MyObserver implements SplObserver {
    public function update(SplSubject $subject, $eventData = null) {
        if (is_null($eventData))
            // carefull
    }
}

Ответ 2

It seems like these interfaces are pretty much useless for any real world problem. Can someone enlighten me?

Интерфейс

В то время как абстрактные классы позволяют обеспечить некоторую меру реализации, интерфейсы являются чистыми шаблонами. interface может define functionality; он никогда не сможет его реализовать. Интерфейс объявляется с помощью ключевого слова интерфейса. Он может содержать объявления свойств и методов, но не тела методов.

Использование интерфейса

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

By itself, interfaces are not very useful, потому что вы не можете создавать экземпляры интерфейсов, но интерфейсы играют важную роль в обеспечении объектно-ориентированных методологий дизайна which in real sense makes your live easier as a programmer, потому что основным стимулом для объектно-ориентированного программирования является инкапсуляция (вам все равно, как реализована возможность. как программист, отображаются только для интерфейса. Это также хороший способ наблюдать за архитектурой системы)

SplSubject и SplObserver

Ортогональность - это добродетель. Одна из целей, по которой программисты должны создавать компоненты, которые могут быть изменены или перемещены с минимальным воздействием на другие компоненты.

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

Нет специальной функции SplSubject и SplObserver, потому что вы оба interface to implement the Observer Design Pattern.

Шаблон наблюдателя

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

  • Шаблон Observer определяет зависимость "один-ко-многим" между объектом-объектом и любым количеством объектов-наблюдателей, поэтому, когда объект объекта изменяется, все его объекты-наблюдатели уведомляются и обновляются автоматически.
  • Шаблон Observer, по существу, позволяет неограниченному количеству объектов наблюдать или прослушивать события в наблюдаемом объекте (или субъекте), регистрируя себя. После того, как наблюдатели будут зарегистрированы в событии, субъект уведомит их об этом при запуске события.
  • Объект справляется с этим, сохраняя коллекцию наблюдателей и итерации через нее, когда происходит событие, чтобы уведомить каждого наблюдателя.
  • Шаблон наблюдателя регистрирует наблюдателей с объектом.
  • У вас может быть несколько наблюдателей. Субъект должен вести список зарегистрированных наблюдателей, и когда происходит событие, он запускает (предоставляет уведомление) всем зарегистрированным наблюдателям.
  • Отменить регистрацию также возможно, когда нам не нужен какой-либо наблюдатель.

Пример 1. Система уведомления о процентной ставке для займа

$loan = new Loan("Mortage", "Citi Bank", 20.5);
$loan->attach(new Online());
$loan->attach(new SMS());
$loan->attach(new Email());

echo "<pre>";
$loan->setIntrest(17.5);

Выход

Online    : Post online about modified Intrest rate of : 17.50
Send SMS  : Send SMS to premium subscribers : 17.50
Send Email: Notify mailing list : 17.50

Пример 2. Простой монитор регистра пользователей

$users = new Users();

new Audit($users);
new Logger($users);
new Security($users);

$users->addUser("John");
$users->addUser("Smith");
$users->addUser("Admin");

Выход

Audit    : Notify Audit about John
Log      : User John Create at Wed, 12 Dec 12 12:36:46 +0100
Audit    : Notify Audit about Smith
Log      : User Smith Create at Wed, 12 Dec 12 12:36:46 +0100
Audit    : Notify Audit about Admin
Log      : User Admin Create at Wed, 12 Dec 12 12:36:46 +0100
Security : Alert trying to create Admin

Преимущество шаблона проектирования Observer: Основным преимуществом является свободная связь между объектами, называемыми наблюдателями и наблюдаемыми. Субъект знает только список наблюдателей, которым он не заботится о том, как у них есть их реализация. Все наблюдатели уведомляются субъектом в одном вызове события как широковещательная связь

Недостаток шаблона проектирования Observer:

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

Общие классы

abstract class Observable implements SplSubject {
    protected $_observers = [];

    public function attach(SplObserver $observer) {
        $id = spl_object_hash($observer);
        $this->_observers[$id] = $observer;
    }

    public function detach(SplObserver $observer) {
        $id = spl_object_hash($observer);

        if (isset($this->_observers[$id])) {
            unset($this->_observers[$id]);
        }
    }

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



abstract class Observer implements SplObserver {
    private $observer;

    function __construct(SplSubject $observer) {
        $this->observer = $observer;
        $this->observer->attach($this);
    }
}

Загрузить классы классов

class Loan extends Observable {
    private $bank;
    private $intrest;
    private $name;

    function __construct($name, $bank, $intrest) {
        $this->name = $name;
        $this->bank = $bank;
        $this->intrest = $intrest;
    }

    function setIntrest($intrest) {
        $this->intrest = $intrest;
        $this->notify();
    }

    function getIntrest() {
        return $this->intrest;
    }
}

class Online implements SplObserver {

    public function update(SplSubject $loan) {
        printf("Online    : Post online about modified Intrest rate of : %0.2f\n",$loan->getIntrest());
    }
}

class SMS implements SplObserver {

    public function update(SplSubject $loan) {
        printf("Send SMS  : Send SMS to premium subscribers : %0.2f\n",$loan->getIntrest());
    }
}

class Email implements SplObserver {

    public function update(SplSubject $loan) {
        printf("Send Email: Notify mailing list : %0.2f\n",$loan->getIntrest());
    }
}

Примеры классов пользователей

class Users extends Observable {
    private $name;

    function addUser($name) {
        $this->name = $name;
        $this->notify();
    }

    function getName() {
        return $this->name;
    }
}
class Audit extends Observer {

    public function update(SplSubject $subject) {
        printf("Audit    : Notify Autify about %s\n", $subject->getName());
    }
}
class Logger extends Observer {

    public function update(SplSubject $subject) {
        printf("Log      : User %s Create at %s\n", $subject->getName(),date(DATE_RFC822));
    }
}
class Security extends Observer {
    public function update(SplSubject $subject) {
        if($subject->getName() == "Admin")
        {
            printf("Security : Alert trying to create Admin\n");
        }
    }
}

Ответ 3

Это довольно просто: шаблон subject/observer не полезен для системы событий.

Шаблон наблюдателя не поддается утверждению: "Эта вещь была обновлена ​​X". Вместо этого он просто говорит, что он был обновлен. Я фактически создал гибкий класс посредника, который можно было бы использовать для системы событий. В зависимости от ваших потребностей может оказаться полезным более жесткий API, но вы можете использовать его как вдохновение.

Итак, когда применим шаблон субъекта/наблюдателя?

Это довольно распространенный шаблон при обновлении GUI, потому что какой-то объект изменился. На самом деле не нужно знать, что изменило его или почему, просто нужно его обновить. Характер HTTP действительно не поддается этой конкретной схеме, потому что ваш PHP-код не привязан непосредственно к HTML. Вы должны сделать новый запрос, чтобы обновить его.

Короче говоря, шаблон Subject/Observer на самом деле не так полезен в PHP. Кроме того, интерфейс не так полезен, потому что вы используете instanceof для получения подходящего типа объекта. Я бы просто написал свой собственный интерфейс и не имел дело с ним.

Ответ 4

Эти два интерфейса не имеют магических функций, связанных с ними, поэтому их реализация ничего не делает. Они действительно используются только в справочных целях. Существуют и другие внутренние интерфейсы PHP, такие как SeekableIterator. Не существует волшебной функциональности, связанной с методом seek, и вы должны реализовать ее самостоятельно.

Есть некоторые внутренние интерфейсы PHP, такие как Traversable, которые получают специальную функциональность, но это не относится к SplSubject и SplObserver - это, по сути, только предлагаемый интерфейс для реализации шаблона Observer.

Что касается того, что произошло, эта информация не является частью интерфейса, поскольку она не абстрактна. Это для вас, чтобы реализовать.

interface Event extends SplSubject {
   public function getEventData();
}

class MyEvent implements Event {
   //MySubject implementation above
   public function getEventData() {
      return "this kind of event happened";
   }
}

Вы также можете полностью игнорировать интерфейс Event или просто использовать instanceof проверки (уродливые), чтобы увидеть, какой тип "Subject" передается методу.

Что касается примера реального мира, эта ссылка дает один, хотя использование SplObserver/SplSubject не является строго необходимым; они все-таки интерфейсы. По существу, вы могли бы иметь ExceptionHandler тематический класс и некоторые наблюдатели, например, Mailer. Вы можете использовать set_exception_handler(array($handler, 'notify'));, и любое исключение, которое выдается, уведомляет обо всех наблюдателях (например, Mailer), который отправляет электронное письмо об исключении, которое было обнаружено, - вам нужно получить исключение из другого метода/члена ExceptionHandler).

EDIT: Я вижу из комментариев, что вы планируете использовать другой аргумент для update, чтобы передать событие как отдельный объект. Я предполагаю, что все в порядке, но мое предложение состоит в том, чтобы не разделить понятия Subject и Event и дать субъекту возможность либо содержать данные событий, либо сами данные о событиях. Вам нужно будет проверить, что объект события, который вы получили, не является нулевым.

Ответ 5

Как любой интерфейс, он бесполезен, пока вы его не реализуете. Внедряя те, вы можете использовать приложение, управляемое событиями

Представьте, что у вас есть событие "applicationStart", вам нужно запустить 10 функций.

function applicationStart() {
   // Some other logic 
   fnCall1();
   fnCall2();
   fnCall3();
   fnCall4();
   fnCall5();
   fnCall6();
   fnCall7();
   fnCall8();
   fnCall9();
   fnCall10();
   // Some other logic 
}

Теперь представьте, что вам нужно протестировать эту функцию, вы будете запускать зависимость от всех остальных 10 функций.

Если вы используете SplSubject/SplObserver:

function applicationStart() {
    // Logic
    $Subject->notify();
    // Logic
}

Теперь, когда вы проверяете это, вам просто нужно убедиться, что вы вызываете событие. Без выполнения других функций.

Плюс код выглядит более чистым, так как вы не делаете его с помощью бизнес-логики, которая там не принадлежит. И одно приятное место для добавления триггеров

Ответ 6

Взгляните на https://github.com/thephpleague/event, это очень хорошо работает. Я считаю, что это лучший пакет сегодня для этой цели. Я также не вижу значения в

public function notify(/* without args */) {

С лигой/событием у вас будет следующее. Например, у меня есть список адресов электронной почты и вы хотите обрабатывать события, когда в список добавляется новое письмо.

class EmailList
{
    const EVENT_ADD_SUBSCRIBER = 'email_list.add_subscriber';
    public function __construct($name, $subscribers = [])
    {
        // do your stuff 
        $this->emitter = new Emitter(); 
    }


    /**
     * Adds event listeners to this list
     * @param $event
     * @param $listener
     */
     public function addListener($event, $listener)
     {
         $this->emitter->addListener($event, $listener);
     } 

    /**
     * Adds subscriber to the list
     * @param Subscriber $subscriber
     */
    public function addSubscriber(Subscriber $subscriber)
    {
        // do your stuff 
        $this->emitter->emit(static::EVENT_ADD_SUBSCRIBER, $subscriber);
    }
}

// then in your code
$emailList = new EmailList();
$emailList->addListener(EmailList::EVENT_ADD_SUBSCRIBER, function($eventName, $subscriber) {
});