20

标准 PHP 库通过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();

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


编辑:

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

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

4

6 回答 6

38

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 & SplObserver

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

如果您对一个组件所做的每一次更改都需要在代码库的其他地方进行一系列更改,那么开发任务很快就会变成一个螺旋式的错误创建和消除。

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

观察者模式

观察者模式是一种软件设计模式,其中一个称为主体的对象维护其依赖项列表,称为观察者,并自动通知他们任何状态更改,通常通过调用他们的方法之一。主要用于实现分布式事件处理系统

  • 观察者模式定义了一个主题对象和任意数量的观察者对象之间的一对多依赖关系,这样当主题对象改变状态时,它的所有观察者对象都会自动得到通知和更新。
  • 观察者模式本质上允许无限数量的对象通过注册自己来观察或监听被观察对象(或主题)中的事件。观察者注册到事件后,主体将在事件触发时通知他们。
  • 主体通过存储观察者集合并在事件发生时迭代它以通知每个观察者来处理此问题。
  • 观察者模式将观察者注册到一个主题。
  • 您可能有多个观察者。主体必须保留已注册观察者的列表,并且当事件发生时,它会触发(提供通知)所有已注册的观察者。
  • 当我们不需要任何观察者时,也可以取消注册。

示例 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");
        }
    }
}
于 2012-12-12T12:11:46.850 回答
14

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

观察者模式不适合说“这个东西是由 X 更新的”。相反,它只是说它已更新。我实际上创建了一个灵活的中介类,可以用于事件系统。根据您的需要,更严格的 API 可能会有所帮助,但您可以以此为灵感。

那么主题/观察者模式什么时候有用呢?

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

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

于 2012-12-16T16:55:30.253 回答
7

这两个接口没有附加任何神奇的功能,所以实现它们什么都不做。它们实际上仅用于参考目的。还有其他类似的 PHP 内部接口,例如SeekableIterator. 该方法没有附加任何神奇功能seek,您必须自己实现它。

有一些 PHP 内部接口,例如具有Traversable特殊功能的接口,但情况并非如此,SplSubject并且SplObserver- 它本质上只是一个建议的接口,用于实现观察者模式。

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

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

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

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

于 2012-12-11T22:38:45.273 回答
6

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

class MyObserver implements SplObserver {
    public function update(SplSubject $subject, $eventData = null) {
        if (is_null($eventData))
            // carefull
    }
}
于 2012-12-12T05:25:27.173 回答
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 代码看起来更干净,因为您不会使用不属于那里的业务逻辑来污染它。还有一个添加触发器的好地方

于 2012-12-12T02:01:56.830 回答
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) {
});
于 2017-03-17T15:38:11.610 回答