SplObserver通知问题

时间:2011-12-30 14:57:07

标签: php oop observer-pattern spl

我正在研究SplObserver模式作为解决日志记录问题的一种方法(即如何处理活动日志记录而不直接在您感兴趣的类中实现它,因此将代码放入其中并不直接与他们的责任范围有关)。

问题在于,实施时,SplObserver似乎没有为通知类提供任何类型的标准化机制,而是将任何细节发送到观察类,而不是“我正在触发通知”。

我很好奇其他人如何解决这个问题,他们是扩展SplObserver和SplSubject接口还是自己滚动?

我还想到了更一般的术语(如可以用Observers实现的其他功能,而不是必要的日志记录)如果可以实现观察者模式,观察者可以指定它只是想要通知某些事件,而不是主题可能产生的每个事件。例如,我可能希望记录观察者将所有活动记录到日志文件中,但也需要一个错误报告观察者,它在发生错误时向管理员发送电子邮件,但仅在发生错误时发送。您可以编写错误记录器来忽略不是由错误触发的通知(假设可以修改此模式以便可以发送特定类型的通知),但我怀疑这样做效率低于理想值。我怀疑允许观察者只订阅特定主题事件会更好,但是这种方法可以用SplObserver实现吗?

2 个答案:

答案 0 :(得分:3)

splSubject在发送通知时会自行发送。可以在主题中实现回调方法,以便观察者可以弄清楚究竟发生了什么变化。

function update(SplSubject $subject) {
   $changed = $subject->getChanges();
   ....
}

你可能必须创建一个新的界面来强制主题中存在getChanges()。

在不同类型的通知上,您可以查看消息队列系统。它们允许您订阅不同的消息框('logging.error','logging.warning',甚至'logging'),如果另一个系统(主题)将消息发送到相应的队列,它们将接收通知。它们作为splObserver / splSubject实现起来并不困难。

答案 1 :(得分:0)

通过选择性订阅实现观察者模式:

假设我们有一个 User 存储库类,我们希望观察该类以记录操作并向新用户发送欢迎电子邮件。

class User
{
    public function create()
    {
        // User creation code...
    }

    public function update()
    {
        // User update code...
    }

    public function delete()
    {
        // User deletion code...
    }
}

现在,我们创建一个包含主题逻辑的 trait。此 Trait 可以应用于您想要观察的任何类。它可以管理不同的“事件名称”,因此观察者可以订阅所有事件或仅订阅某些事件。

trait SubjectTrait {

    private $observers = [];

    // this is not a real __construct() (we will call it later)
    public function construct()
    {
        $this->observers["all"] = [];
    }

    private function initObserversGroup(string $name = "all")
    {
        if (!isset($this->observers[$name])) {
            $this->observers[$name] = [];
        }
    }

    private function getObservers(string $name = "all")
    {
        $this->initObserversGroup($name);
        $group = $this->observers[$name];
        $all = $this->observers["all"];

        return array_merge($group, $all);
    }

    public function attach(\SplObserver $observer, string $name = "all")
    {
        $this->initObserversGroup($name);
        $this->observers[$name][] = $observer;
    }

    public function detach(\SplObserver $observer, string $name = "all")
    {
        foreach ($this->getObservers($name) as $key => $o) {
            if ($o === $observer) {
                unset($this->observers[$name][$key]);
            }
        }
    }

    public function notify(string $name = "all", $data = null)
    {
        foreach ($this->getObservers($name) as $observer) {
            $observer->update($this, $name, $data);
        }
    }
}

接下来,我们在类中使用 trait。我们的 User 类将如下所示:

class User implements \SplSubject
{

    // It's necessary to alias construct() because it
    // would conflict with other methods.
    use SubjectTrait {
        SubjectTrait::construct as protected constructSubject;
    }

    public function __construct()
    {
        $this->constructSubject();
    }

    public function create()
    {
        // User creation code...

        $this->notify("User:created");
    }

    public function update()
    {
        // User update code...

        $this->notify("User:updated");
    }

    public function delete()
    {
        // User deletion code...

        $this->notify("User:deleted");
    }
}

最后一步是创建我们的 observer 类,它可以订阅主题。我们在这里实现了两个记录器和一个电子邮件发送器。

class Logger1 implements \SplObserver
{
    public function update(\SplSubject $event, string $name = null, $data = null)
    {
        // you could also log $data
        echo "Logger1: $name.\n";
    }
}

class Logger2 implements \SplObserver
{
    public function update(\SplSubject $event, string $name = null, $data = null)
    {
        // you could also log $data
        echo "Logger2: $name.\n"; 
    }
}

class Welcomer implements \SplObserver
{
    public function update(\SplSubject $event, string $name = null, $data = null)
    {
        // here you could use the user name from $data
        echo "Welcomer: sending email.\n";
    }
}

让我们测试一下:

// create a User object
$user = new User();

// subscribe the logger 1 to all user events
$user->attach(new Logger1(), "all");

// subscribe the logger 2 only to user deletions
$user->attach(new Logger2(), "User:deleted");

// subscribe the welcomer emailer only to user creations
$user->attach(new Welcomer(), "User:created");

// perform some actions
$user->create();
$user->update();
$user->delete();

输出将是:

Welcomer: sending email.
Logger1: User:created.
Logger1: User:updated.
Logger2: User:deleted.
Logger1: User:deleted.