关于php:SplSubject / SplObserver如何有用?

How is SplSubject/SplObserver useful?

标准PHP库包括一些资源所称的观察器模式的引用实现,通过SplSubjectSplObserver类。在我的生活中,我不知道这些是如何非常有用的,没有办法将实际事件或任何其他信息与通知一起传递:

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

对于任何现实世界的问题来说,这些接口似乎都是无用的。有人能启发我吗?

编辑:

这是我对接口最大的问题(尽管还有其他问题):

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

…捕获以下致命错误:

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

编辑第2页:

通过为附加参数指定默认值,使其成为可选参数,可以防止致命错误,并提供传递上下文的方法,使实现变得值得。我以前没有意识到这一点,所以这几乎回答了我的问题。解决方案是传递您自己的事件/消息数据,并检查它在SplObserver::update()中的存在。


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

界面

当摘要类让你提供一些实现的测量时,界面是纯粹的模板。它只能实现它。用接口关键字声明接口。它可以包括特性和方法声明,但不包括方法机构。

接口使用例

例如,如果你想支持你的项目,应该支持不同的数据库。因此,您可以在将来更改您的数据库,更好地使用包含在档案中的属性程序的界面。

因为你不能创建界面实例,而界面是用于执行面向对象的设计方法EDOCX1[…]4的工具,因为对象定向编程的前期激励是封装的(你不在乎一种能力是如何实现的)。你,作为一个程序员,只在界面上展示。这也是系统结构后观察的好方法

脾主体

正交性是一个虚拟的目标,其中一个目标,作为程序员,应该是构建可以更换或移动的组件,对其他组件的影响最小。

如果你每次改变一个成分都需要一个改变的时间表,那么在编码中,发展任务就可以迅速成为错误创造和消除的螺旋。

因为脾和脾脏两种特征都是一样的

观察模式

观察家模式是一种软件设计模式,它被称为一个对象,称为主体,保持一份附属物清单,称为观察家,并自动通知任何状态的变化,通常是用一种方法。主要用于实现分布式事件处理系统。

  • The observer pattern defines an one-to-many dependence between a subject object object and any number of observations so when the subject object object changes state,all its observations are notived and updated a
  • The observer pattern essentially allows an unlimited number of objects to observate or listen to events in the observed object(or subject)by registering themselves.在观察家注册参加某一事件之后,当事件着火时,主体将通知他们。
  • 当事件发生时,主体通过保存一个观察家的收集和编辑来保存这一手柄,以便通知每一个观察家。
  • Observer Pattern Registers Observers with a subject.
  • 你可能有多重观察家。Subject must keep a list of registered observers and when event occures it fires(provides notification)all registered observers.
  • 当我们不需要任何观察家的时候,也有可能进行登记。

Example 1.贷款利率通知系统

1
2
3
4
5
6
7
$loan = new Loan("Mortage","Citi Bank", 20.5);
$loan->attach(new Online());
$loan->attach(new SMS());
$loan->attach(new Email());

echo"[cc lang="php"]";
$loan->setIntrest(17.5);

输出

ZZU1

Example 2.简单用户注册监视器

1
2
3
4
5
6
7
8
9
$users = new Users();

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

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

输出

1
2
3
4
5
6
7
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

观测设计模式的优势:主要优势在于物体被称为观测者和可观测者之间的软耦合。The subject only know the list of observers it don't care about how they have their implementation.All the observers are notified by subject in a single event call as broadcast communication

观察设计模式的缺陷:

  • 不利之处在于,如果出现任何问题,解答变得非常困难,因为控制的流量在观察者和观察者之间是隐含的,我们可以预测,现在的观察者正在着火,如果观察者之间有链条,那么解答变得更加复杂。
  • 另一个问题是与大型观察家交往时的记忆管理。

普通类

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

负载样本类

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
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
"
,$loan->getIntrest());
    }
}

class SMS implements SplObserver {

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

class Email implements SplObserver {

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

用户注册样本类别

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
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
"
, $subject->getName());
    }
}
class Logger extends Observer {

    public function update(SplSubject $subject) {
        printf("Log      : User %s Create at %s
"
, $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
"
);
        }
    }
}


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

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

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

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

简而言之,主题/观察者模式在PHP中并没有那么有用。此外,该接口没有那么有用,因为您已经使用instanceof获得了正确的主题类型。我只写我自己的界面,不处理它。


这两个接口没有附加的魔力功能,因此实现它们什么也不做。它们实际上只用于参考目的。还有其他类似这样的PHP内部接口,比如SeekableIteratorseek方法没有附加的神奇功能,您必须自己实现它。

有一些PHP内部接口,如Traversable获得了特殊的功能,但这不是SplSubjectSplObserver的情况——它本质上只是一个建议的接口,用于实现观察者模式。

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

1
2
3
4
5
6
7
8
9
10
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检查(丑陋)来查看传递给方法的是什么类型的"主题"。

对于一个现实世界的例子,这个链接提供了一个,尽管使用SplObserverSplSubject并不是严格必要的,毕竟它们只是接口而已。本质上,您可以有ExceptionHandler主题类和一些观察者,例如Mailer。您可以使用set_exception_handler(array($handler, 'notify'));,任何抛出的异常都会通知所有观察者(例如Mailer,它发送一封关于捕获到的异常的电子邮件—您必须从ExceptionHandler的其他方法/成员中获取异常)。

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


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

1
2
3
4
5
6
class MyObserver implements SplObserver {
    public function update(SplSubject $subject, $eventData = null) {
        if (is_null($eventData))
            // carefull
    }
}

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

假设您有一个事件"applicationstart",您需要在它上面运行10个函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function applicationStart() {
   // Some other logic
   fnCall1();
   fnCall2();
   fnCall3();
   fnCall4();
   fnCall5();
   fnCall6();
   fnCall7();
   fnCall8();
   fnCall9();
   fnCall10();
   // Some other logic
}

现在假设您需要测试这个函数,您将触发对所有其他10个函数的依赖。

如果使用splsubject/splsobserver:

1
2
3
4
5
function applicationStart() {
    // Logic
    $Subject->notify();
    // Logic
}

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

另外,代码看起来更干净,因为您不会用不属于它的业务逻辑来争论它。还有一个很容易添加触发器的地方


看看https://github.com/thephprigue/event,它做得很好。我认为这是目前最好的包装。我也看不到任何价值

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

在联赛/活动中,您将有以下活动。例如,我有电子邮件列表,希望在新电子邮件添加到列表时处理事件。

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