48

我注意到我的依赖注入、大量观察者模式的代码(使用 Guava 的EventBus)通常比我过去编写的没有这些功能的代码更难调试。特别是在试图确定何时以及为何调用观察者代码时。

Martin Oderski 和他的朋友写了一篇长篇论文,标题特别吸引人,“弃用观察者模式”,我还没有抽出时间阅读它。

我想知道观察者模式有什么问题,以及关于引导这些聪明人写这篇论文的(建议的或其他的)替代方案要好得多。

作为开始,我确实在这里找到了对这篇论文的一个(有趣的)批评。

4

3 回答 3

37

直接从论文中引用:

为了说明观察者模式的确切问题,我们从一个简单而普遍的例子开始:鼠标拖动。以下示例在Path对象中进行拖动操作期间跟踪鼠标的移动并将其显示在屏幕上。为了简单起见,我们使用 Scala 闭包作为观察者。

var path: Path = null
val moveObserver = { (event: MouseEvent) =>
   path.lineTo(event.position)
   draw(path)
}
control.addMouseDownObserver { event =>
   path = new Path(event.position)
   control.addMouseMoveObserver(moveObserver)
}
control.addMouseUpObserver { event =>
   control.removeMouseMoveObserver(moveObserver)
   path.close()
   draw(path)
}

上面的例子,正如我们将争论的那样,通常在 [25] 中定义的观察者模式违反了一系列重要的软件工程原则:

副作用观察者促进副作用。由于观察者是无状态的,我们经常需要其中的几个来模拟一个状态机,就像在拖动示例中一样。我们必须保存所有相关观察者都可以访问的状态,例如path上面的变量。

封装随着状态变量path脱离观察者的范围,观察者模式打破了封装。

可组合性多个观察者形成一个松散的对象集合,这些对象处理一个关注点(或多个关注点,见下一点)。由于多个观察者在不同时间安装在不同点,例如,我们不能轻易地完全处置它们。

关注点分离上述观察者不仅跟踪鼠标路径,还调用绘图命令,或者更一般地,在同一代码位置包含两个不同的关注点。通常最好将构建路径和显示路径的关注点分开,例如在模型-视图-控制器 (MVC) [30] 模式中。

可扩展性 在我们的示例中,我们可以通过为路径创建一个类来实现关注点分离,该类本身在路径更改时发布事件。不幸的是,观察者模式不能保证数据的一致性。让我们假设我们将创建另一个事件发布对象,该对象依赖于我们原始路径的变化,例如,一个代表我们路径边界的矩形。还要考虑一个观察者监听路径及其边界的变化以绘制框架路径。该观察者需要手动确定边界是否已经更新,如果没有,则推迟绘图操作。否则,用户可能会在屏幕上看到尺寸错误的帧(故障)。

一致性安装不同观察器的不同方法会降低代码一致性。

抽象示例中有一个低级别的抽象。它依赖于控件类的重量级接口,该接口提供的不仅仅是安装鼠标事件观察器的特定方法。因此,我们不能抽象出精确的事件源。例如,我们可以让用户通过按下退出键或使用不同的指针设备(如触摸屏或图形输入板)来中止拖动操作。

资源管理观察者的生命周期需要由客户端管理。由于性能原因,我们只想在拖动操作期间观察鼠标移动事件。因此,我们需要显式地安装和卸载鼠标移动观察器,并且我们需要记住安装点(上面的控制)。

语义距离最终,这个例子很难理解,因为控制流是倒置的,导致过多的样板代码增加了程序员意图和实际代码之间的语义距离。

[25] E. Gamma、R. Helm、R. Johnson 和 J. Vlissides。设计模式:可重用的面向对象软件的元素。Addison-Wesley Longman Publishing Co., Inc.,美国马萨诸塞州波士顿,1995 年。ISBN 0-201-63361-2。

于 2012-07-24T13:54:17.110 回答
18

我相信观察者模式具有解耦事物所带来的标准缺点。Subject 与 Observer 分离,但您不能只查看它的源代码并找出谁在观察它。硬编码的依赖项通常更容易阅读和思考,但它们更难修改和重用。这是一个权衡。

至于论文,它并没有解决观察者模式本身,而是它的特定用法。特别是:每个被观察的单个对象有多个无状态观察者对象。这有一个明显的缺点,即需要相互同步的独立观察者(“由于观察者是无状态的,我们经常需要其中的几个来模拟状态机,就像在拖动示例中一样。我们必须将状态保存在可以访问的位置所有涉及的观察者,例如上面的变量路径。 ")

上述缺点是特定于这种用法,而不是观察者模式本身。您也可以创建一个(有状态的!)观察者对象来实现所有OnThis, OnThat,OnWhatever方法,并摆脱在许多无状态对象中模拟状态机的问题。

于 2012-07-24T07:28:39.593 回答
8

我会很简短,因为我是这个话题的新手(并且还没有阅读那篇具体的文章)。

观察者模式在直觉上是错误的:被观察对象知道谁在观察(Subject<>--Observer)。这与现实生活背道而驰(在基于事件的场景中)。如果我尖叫,我不知道谁在听;如果闪电击中地板……闪电直到击中才知道有地板!只有观察者知道他们可以观察到什么。

当这种事情发生时,软件就会变得一团糟——因为违背了我们的思维方式——。就好像和对象知道其他对象可以调用他的方法一样。

IMO 诸如“环境”之类的层是负责处理事件并通知受影响者的层。(或混合事件和该事件的生成器)

事件源(主题)向环境生成事件。环境将事件传递给观察者。观察者可以注册影响他的事件类型,或者它实际上是在环境中定义的。这两种可能性都是有意义的(但我想简明扼要)。

据我了解,观察者模式将环境和主题放在一起。

PS。讨厌把抽象的想法放在段落里!:P

于 2012-12-13T09:47:05.403 回答