什么是使用Symfony的EventDispatcher组件的正确方法?

时间:2012-03-19 19:11:11

标签: php oop design-patterns symfony1

我想通过使某些类可观察来促进我的PHP代码中的松散耦合。 Symfony的EventDispatcher component看起来很有希望,SPL SplObserver / {{ 3}}一对类。

最好的方法是什么?我可以看到几种不同的可能性:

(1)将EventDispatcher实例注入每个可观察类(跟踪全局EventDispatcher实例):

class Foo
{
    public function __construct($dispatcher)
    {
        $this->dispatcher = $dispatcher;
    }

    public function bar()
    {
        $this->dispatcher->dispatch(...);
    }
}

(2)让observable类扩展EventDispatcher类:

class Foo extends EventDispatcher
{
    public function bar()
    {
        $this->dispatch(...);
    }
}

(3)使用SplObserver / SplSubject - 直截了当,但不如EventDispatcher组件灵活

2 个答案:

答案 0 :(得分:48)

免责声明:此答案与Symfony EventDispatcher无关,但与您的问题有关。如果你只是想要一个答案,你可以跳过(有点)学术讨论并跳到最后。

讨论

  

FACT :增加应用程序规模意味着复杂性的一致性增加。

随着应用程序范围的扩大,您会发现自己添加了越来越多的类来实现必需的功能。突然之间,记住Foo对象在创建Bar对象时需要执行某些特定操作并不容易。此外,当您的对象开始为彼此提供互补功能时,维持必要的关系变得越来越困难,而不会使用非常紧密耦合的对象。

我们需要一种方法让对象能够进行通信,而无需硬编码显式引用,当某些内容发生变化时我们会忘记改变这些引用。那么我们如何在快速增长的对象图的节点之间管理这种相互关联的功能呢?

如果你想要它持久,你必须谈谈

让我们稍微绕道一下,考虑一个浪漫的比喻...

任何关系都需要一致的沟通,如果它会持续下去。当然,你和你的伴侣可以在星期六晚上聚在一起进行阴暗的连接,而不是在一周剩下的时间里互相交谈。然而,这种类型的沟通通常会导致脆弱的关系,在这种关系中,任何一方都不了解对方在关系背景下实际需要运作的内容。

继续比喻,随着你的个性随着时间的推移慢慢变化(而且会发生),这种缺乏沟通会阻止你的伴侣理解如何最好地与你互动。最终,所有破碎的承诺和未接来电都成了头,这种关系再也不起作用了。它坏了。

您的应用程序以相同的方式工作。代码应该足够成熟,可以说,"嘿宝贝,我可能会改变,但如果我这样做,我保证我会一直让你知道我在做什么。"不幸的是,随着复杂性的增加,传统的直线应用程序设计使得这种通信很难在没有类之间紧密耦合的情况下维持。

进入活动管理

这就是事件管理的全部内容。我们的目标是为我们的对象提供一种彼此通信的方式,这种方式不会与他们需要与之通信的对象进行硬编码关系。与大多数编程问题一样,这不是一个单一的,具体的,正确的"这样做的方式。您的问题特别提到了实现此目的的两种可用方法,因此我将解决这些问题。如果您想了解其他一些选项,@ ircmaxell最近发布了a nice survey blog post about making PHP apps "pluggable"

观察

在实践中,您发现Observer模式的实际PHP应用程序很少。这是因为如果您希望您的代码非常动态,那么在您将观察者附加到所有地方的主题对象之前不需要很长时间。

当发生这种情况时,您已经开始尝试实现松散耦合,但您已经创建了一种不同类型的问题:手动附加所有观察者和主体 。例如,如果应用程序中的每个类都是Logger观察者对象的主题,那么您已经为自己创建了大量工作。此外,恕我直言这种方法有时会通过将可能更准确地描述为主题的实际依赖性的内容移出主题构造函数的方法签名来模糊您的API。

如果我们使用集中式调度程序在事件发生时通知感兴趣的对象,我们的应用程序会更加灵活,尽管Observer模式可以适用于一次性或简单的情况。

中保

管理事件的更强大的方法是插入一个集中层来处理调度事件到适当的侦听器。这就是Mediatorwiki模式(和Symfony事件调度程序)的作用。

Mediator的观点在于它是系统中每个事件的集中式中转站,因此需要在整个应用程序范围内(或者介导的部分)访问它。请注意,意味着您应该将其视为全局变量,并使用全局关键字无条件地访问Mediator,或将其包装在某种邪恶的单例对象或静态属性/方法中。这种滥用将导致@liquorvicar在第一个答案中提出的问题。但是,我强烈不同意该答案的评估:

  

"在您的应用程序中随处可见的eventDispatcher几乎可以使您的代码更难以测试/理解/维护等(它可以接近上帝对象)"

只有在您滥用调解员的情况下才会这样;它应该发送事件通知而不是其他任何东西。出于这个原因,我会提醒您不要像你在选项(2)中建议的那样延长它。正确使用时,中介对象极其可测试。没有什么比模拟构造函数中指定的依赖项对象的行为更简单了。这就是单元测试的全部内容。

答案

因此,如果您需要在应用程序中进行非线性事件管理,我强烈建议您从问题中选择(1)。只要你不滥用它,它就完全可以接受。对Symfony实现进行着色,它似乎支持任何PHP可调用作为监听器。就个人而言,我更喜欢一个系统,它允许基于类的侦听器的惰性实例化,以实现更高效和面向对象的范例,但实现细节由您决定。

责任链模式与Mediator密切相关,是实现类似结果的另一种有效方法。如果您有兴趣,我建议先前发布@ ircmaxell博客文章中的链接。

答案 1 :(得分:2)

我会避免(2)。继承可能是 最常用的模式,可能与此无关。选项(1)和(3)之间的选择可能取决于您的上下文。虽然避免紧耦合是好的,但你应该警惕瑞士军刀解决方案。拥有一个eventDispatcher,它在你的应用程序中无处不在,几乎所有东西都可以让你的代码更难以测试/理解/维护等(它可以接近一个God对象)。另一方面,Spl解决方案更简单,因此如果您确实需要多个观察者/可观察者,您可能会发现必须维护太多的SplObservers / SplSubjects。

与OOP中的大多数内容一样,没有最佳方式,通常取决于您的确切用例......