我正在研究SplObserver模式作为解决日志记录问题的一种方法(即如何处理活动日志记录而不直接在您感兴趣的类中实现它,因此将代码放入其中并不直接与他们的责任范围有关)。
问题在于,实施时,SplObserver似乎没有为通知类提供任何类型的标准化机制,而是将任何细节发送到观察类,而不是“我正在触发通知”。
我很好奇其他人如何解决这个问题,他们是扩展SplObserver和SplSubject接口还是自己滚动?
我还想到了更一般的术语(如可以用Observers实现的其他功能,而不是必要的日志记录)如果可以实现观察者模式,观察者可以指定它只是想要通知某些事件,而不是主题可能产生的每个事件。例如,我可能希望记录观察者将所有活动记录到日志文件中,但也需要一个错误报告观察者,它在发生错误时向管理员发送电子邮件,但仅在发生错误时发送。您可以编写错误记录器来忽略不是由错误触发的通知(假设可以修改此模式以便可以发送特定类型的通知),但我怀疑这样做效率低于理想值。我怀疑允许观察者只订阅特定主题事件会更好,但是这种方法可以用SplObserver实现吗?
答案 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.