带有选择性订阅的SplObserver模式的实现



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

问题是,SplObserver似乎没有给你任何标准化的机制来通知类发送任何细节到观察类,除了"我正在触发一个通知"。

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

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

splSubject在发送通知时发送自身。可以在主题中实现一个回调方法,这样观察者就可以弄清楚到底发生了什么变化。

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

,您可能必须创建一个新接口来强制在主题中存在getChanges()。

对于不同类型的通知,您可以查看消息队列系统。它们允许您订阅不同的消息框("日志记录")。错误"、"日志记录。警告',甚至'日志'),如果另一个系统(主题)向相应队列发送消息,它们将接收通知。它们并不比splObserver/splSubject更难实现。

观察者模式的实现,带有选择性订阅:

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

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

现在,我们创建一个包含Subject逻辑的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.

最新更新