为什么要弃用观察者模式?

Jef*_*rod 39 oop design-patterns observer-pattern

我注意到我的依赖注入,观察者模式繁重的代码(使用Guava的EventBus)通常比我过去编写的没有这些功能的代码更难调试.特别是在尝试确定调用观察者代码的时间和原因时.

马丁·奥德斯基和朋友们写了一篇冗长的论文,题目是"弃用观察者模式",我还没有花时间阅读它.

我想知道观察者模式的错误是什么,以及更好地引导这些聪明人写这篇论文的(建议的或其他的)替代方案.

首先,我确实在这里找到了一篇(有趣的)论文批评.

Jef*_*rod 33

直接从论文引用:

为了说明观察者模式的精确问题,我们从一个简单且无处不在的例子开始:鼠标拖动.以下示例跟踪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)
}
Run Code Online (Sandbox Code Playgroud)

上面的例子,正如我们将论证[25]中定义的观察者模式一般,违反了一系列令人印象深刻的重要软件工程原则:

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

封装当状态变量path超出观察者的范围时,观察者模式会破坏封装.

可组合性多个观察者形成一个松散的对象集合,处理单个问题(或多个,见下一点).由于多个观察者在不同时间安装在不同的点,因此我们不能轻易地将它们完全丢弃.

关注点分离上述观察者不仅跟踪鼠标路径而且还调用绘图命令,或者更一般地,在同一代码位置中包括两个不同的关注点.通常优选的是分离构造路径和显示路径的问题,例如,如在模型 - 视图 - 控制器(MVC)[30]模式中那样.

Scalablity我们可以通过为路径创建一个类来实现问题的分离,这些路径在路径发生变化时自己发布事件.遗憾的是,无法保证观察者模式中的数据一致性.让我们假设我们将创建另一个依赖于原始路径变化的事件发布对象,例如,表示路径边界的矩形.还要考虑观察者收听路径及其边界的变化以绘制框架路径.该观察者将手动需要确定边界是否已经更新,如果不是,则推迟绘制操作.否则,用户可能会在屏幕上观察到尺寸错误的帧(毛刺).

均匀性安装不同观察者的不同方法会降低代码均匀性.

抽象示例中的抽象级别较低.它依赖于控件类的重量级接口,它不仅提供安装鼠标事件观察器的特定方法.因此,我们无法抽象出精确的事件来源.例如,我们可以通过点击转义键或使用其他指针设备(如触摸屏或图形输入板)让用户中止拖动操作.

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

语义距离最终,该示例很难理解,因为控制流被反转,导致过多的样板代码增加了程序员意图与实际代码之间的语义距离.

[25] E. Gamma,R.Helm,R.Johnson和J. Vlissides.设计模式:可重用的面向对象软件的元素.Addison-Wesley Longman Publishing Co.,Inc.,Boston,MA,USA,1995.ISBN 0-201-63361-2.

  • 我想知道这里列出的弱点是否是观察者模式固有的。我刚刚阅读了 MobX 库的描述,我相信它使用了观察者模式,但这似乎解决了上述的许多问题。例如,MobX 似乎确实包含对值的一致性和“新鲜度”以及它们更新顺序的保证。请参阅[此处](https://hackernoon.com/the-fundamental-principles-behind-mobx-7a725f71f3e8)。好奇别人怎么想... (2认同)
  • 我不会对所有这些进行评论,但是可以通过使用正确的模式来缓解许多问题,即在上面定义路径的情况下,观察者的变更管理器。变更经理是调解人。所以我认为你的例子有点像稻草人。 (2认同)

Raf*_*ird 9

我相信观察者模式具有与解耦事物相关的标准缺点.主题与观察者分离,但你不能只看它的源代码并找出谁观察它.硬编码的依赖关系通常更容易阅读和思考,但它们更难以修改和重用.这是一个权衡.

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

上述缺点特定于这种用法,而不是Observer模式本身.你还可创建一个实现所有单(有状态!)观测对象OnThis,OnThat,OnWhatever方法和摆脱在许多无状态的对象模拟状态机的问题.


Lui*_*ano 6

我会简短,因为我是这个主题的新手(并没有阅读那篇特定的文章).

观察者模式直观错误:要观察的对象知道谁在观察(主体<> - 观察者).这与现实生活相反(在基于事件的情景中).如果我尖叫,我不知道谁在听; 如果闪电,击中地板......闪电不知道有地板,直到它击中!只有观察者知道他们能观察到什么.

当这种事情发生时,软件就会变得混乱 - 因为这与我们的思维方式相对立.好像和对象知道其他对象可以称之为他的方法.

IMO是一个"环境"层,负责接收事件并通知受影响的人.(或混合事件和该事件的生成器)

Event-Source(Subject)为环境生成事件.环境将事件传递给Observer.观察者可以注册影响他的事件,或者它实际上是在环境中定义的.两种可能性都有意义(但我希望简短).

在我的理解中,观察者模式将环境和主题放在一起.

PS.讨厌提出段落抽象的想法!:P