【问题标题】:Why should the observer pattern be deprecated?为什么要弃用观察者模式?
【发布时间】:2012-07-22 02:39:36
【问题描述】:

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

Martin Oderski 和朋友写了一篇长篇论文,标题特别诱人,"Deprecating the Observer Pattern",我还没有时间阅读。

我想知道观察者模式有什么问题,以及更好地了解(建议或其他)替代方案来引导这些聪明人写这篇论文。

一开始,我确实找到了一篇(有趣的)对论文 here 的评论。

【问题讨论】:

标签: oop design-patterns observer-pattern


【解决方案1】:

直接引用the paper:

为了说明观察者模式的精确问题, 我们从一个简单而普遍的例子开始:鼠标拖动。 下面的例子追踪了 在 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] 模式中一样。

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

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

抽象示例中的抽象级别较低。 它依赖于控件的重量级接口 不仅提供特定安装方法的类 鼠标事件观察者。因此,我们不能抽象 在精确的事件源上。例如,我们 可以让用户通过点击转义来中止拖动操作 键或使用不同的指针设备,例如触摸 屏幕或绘图板。

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

语义距离最终,这个例子很难理解 因为控制流是反转的,结果 在太多增加语义的样板代码中 程序员的意图与 实际代码。

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

【讨论】:

  • 我想知道这里列出的弱点是否是观察者模式固有的。我刚刚通读了 MobX 库的描述,我相信它使用了观察者模式,但这似乎解决了上述很多问题。例如,似乎 MobX 确实包含了对值的一致性和“新鲜度”以及它们更新的顺序的保证。见here。好奇别人怎么想...
  • 我不会对所有这些都发表评论,但是可以通过使用正确的模式来缓解许多问题,例如,在上面定义路径的情况下,为观察者使用变更管理器。变更经理是调解人。所以我认为你的例子有点像稻草人。
  • 无法再访问答案中的链接......但问题中的链接是(并且应该是相同的)
  • 有没有人举例说明这种情况如何更好地使用现在推荐的PropertyChangeEvent
  • @jeff 谢谢。你更喜欢观察者的什么模式?通过关闭授权?
【解决方案2】:

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

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

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

【讨论】:

  • 但是示例中的观察者 do 显然具有您引用中提到的副作用。 (查看path def 的所有操作)。
【解决方案3】:

我会简短一些,因为我是这个主题的新手(还没有阅读具体的文章)。

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

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

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

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

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

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

【讨论】:

  • 这里marco.panizza.name/dispenseTM/slides/exerc/eventNotifier/… 我找到了基本相同的图表:-Publisher(主题)-Subscriber(对象)-EventService(环境)(1998)毕竟“常识”导致它我们在编程时以一种或另一种方式实现这些中间层。当模式激发或误导时,问题就来了。
  • 主题不知道他们的观察者(也许只通过基类)。设计模式书还提到了变更管理器。这与您的环境有些相似。恕我直言,观察者模式非常适合通知(独立)GUI 元素,但该模式也有一些严重的缺点:当主题或业务层处于不一致状态时的虚假通知;通知更新期间观察者更改主题。
  • 这里有几点,主体对观察者一无所知,我们可以通过抽象接口来实现。
猜你喜欢
  • 2012-05-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-20
  • 2023-04-10
相关资源
最近更新 更多