SplSubject / SplObserver如何有用?

时间:2012-12-08 03:26:40

标签: php observer-pattern spl

Standard PHP Library包括一些资源通过SplSubjectSplObserver类调用Observer模式的参考实现。对于我的生活,我无法弄清楚这些是如何非常有用的,无法将实际事件或任何其他信息与通知一起传递:

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();

似乎这些接口对于任何现实世界的问题都是无用的。有人可以开导我吗?


修改

这是我界面的最大问题(尽管还有其他问题):

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()内检查它是否存在。

6 个答案:

答案 0 :(得分:36)

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

<强>接口

虽然抽象类允许您提供一些实现措施,但接口是纯模板。 interface只能define functionality;它永远不会实现它。使用interface关键字声明接口。它可以包含属性和方法声明,但不包含方法体。

界面用例

例如,如果您希望您的项目应该支持不同的数据库。以便以后可以更改您的数据库,以便更好地使用包含类文件中的属性过程的接口而不更改对象

By itself, interfaces are not very useful因为您无法创建接口实例,但接口有助于实施面向对象的设计方法which in real sense makes your live easier as a programmer,因为面向对象编程的最重要的激励是封装(你不关心如何实现了一项功能。作为一名程序员,你只能接触到这个界面。这也是一种在系统架构之后观察的好方法)

SplSubject&amp; SplObserver

正交性是一种美德,作为程序员的目标之一应该是构建可以改变或移动的组件,而对其他组件的影响最小。

如果您对一个组件所做的每一项更改都需要在代码库中的其他位置发生一系列变化,那么开发任务很快就会成为创建和消除错误的螺旋。

SplSubjectSplObserver没有特殊功能,因为你是interface to implement the Observer Design Pattern.

Observer 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

观察者设计模式的优势: 主要优点是称为观察者和观察者的对象之间的松散耦合。主题只知道它不关心如何实现它们的观察者列表。所有观察者都在一个事件呼叫中被主体通知为广播通信

观察者设计模式的缺点:

  • 缺点是有时候如果出现任何问题,调试变得非常困难,因为控制流隐含在观察者和观察者之间我们可以预测现在观察者将要开火,如果观察者之间存在链条,那么调试变得更加复杂
  • 另一个问题是处理大型观察员时的内存管理

常见课程

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");
        }
    }
}

答案 1 :(得分:14)

这很简单:主题/观察者模式对事件系统无用。

观察者模式不适合说“这件事由X更新”。相反,它只是说它已更新。我实际创建了a flexible mediator class,可用于事件系统。根据您的需要,更严格的API可能会有所帮助,但您可以将其用作灵感。

那么主题/观察者模式何时有用?

更新GUI时这是一种相当常见的模式,因为某些对象发生了变化。它并不需要知道是什么改变了它或为什么,只需它需要更新。 HTTP的本质并不适合这种特定模式,因为您的PHP代码不直接与HTML绑定。您必须提出更新请求。

简而言之,Subject / Observer模式在PHP中并没有那么有用。此外,界面没有那么有用,因为您使用instanceof来获取正确的主题类型。我只是编写自己的界面而不是处理它。

答案 2 :(得分:7)

这两个接口没有附加魔术功能,因此实现它们什么都不做。它们实际上仅用于参考目的。还有其他像这样的PHP内部接口,例如SeekableIteratorseek方法没有附加魔术功能,您必须自己实现它。

有一些PHP内部接口,例如Traversable,它们具有特殊功能,但SplSubjectSplObserver并非如此 - 它基本上只是一个建议的接口。观察者模式的实施。

至于发生了什么,该信息不是界面的一部分,因为它不是抽象的。由你来实施。

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检查(丑陋)来查看将哪种“主题”传递给该方法。

至于一个真实世界的例子,this link提供了一个例子,尽管使用SplObserver / SplSubject并非绝对必要;毕竟它们只是接口。从本质上讲,您可以拥有ExceptionHandler主题类和一些观察者,例如Mailer。您可以使用set_exception_handler(array($handler, 'notify'));并且抛出的任何异常都会通知所有观察者(例如Mailer,它发送有关捕获的异常的电子邮件 - 您必须从其他方法/成员获取异常ExceptionHandler)。

编辑:我从评论中看到您打算使用另一个参数来update将事件作为单独的对象传递。我想这没关系,但我的建议只是不分开主题和事件概念,让主体能够包含事件数据或者是事件数据本身。您必须检查您收到的事件对象是否为空。

答案 3 :(得分:6)

您可以使用可选参数实现update方法,并且仍然满足SplSubject接口。

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

答案 4 :(得分:1)

作为任何接口,在实现之前它是无用的。通过实施那些,你可以有事件驱动的应用程序

想象一下,您有一个事件“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
}

现在,当你测试它时,你只需要确保触发事件。没有执行其他功能。

Plus代码看起来更干净,因为您没有使用不属于那里的业务逻辑来对其进行调查。还有一个很容易添加触发器的好地方

答案 5 :(得分:0)

看看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) {
});