为什么不应该忽略观察员模式? [英] Why should the observer pattern be deprecated?

查看:139
本文介绍了为什么不应该忽略观察员模式?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我注意到我的依赖注入了观察者模式重的代码(使用Guava的 EventBus )通常比没有这些功能的代码更难以调试。特别是当尝试确定何时和为什么观察员代码被调用。



Martin Oderski和朋友们写了一篇冗长的论文,具有特别诱人的标题,弃用观察者模式,我还没有时间阅读它。



我想知道观察者模式有什么问题,对于提出或其他替代方案,让这么聪明的人写这篇文章,这个方法要好得多。



作为一个开始,我确实发现了一个(有趣的)对论文的评论 here

解决方案

直接从论文



为了说明精确观察者模式的问题,
我们从一个简单而无处不在的例子开始:鼠标拖动。
以下示例在 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],违反令人印象深刻的
重要的软件工程原则:



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



封装由于状态变量路径转义观察者的范围
,观察者模式将打破封装



可组合性多个观察者构成一个松散的收集
的对象,处理单个关注(或多个,
看下一点)。由于多个观察者在不同时间以不同点的价格安装了
,所以我们不能像b $ b那样轻松处理它们。



分离问题上述观察者不仅跟踪
的鼠标路径,还可以调用绘图命令,或者更一般地说,

相同的代码位置中包含两个不同的关注点。通常优选的是分开构造路径和显示路径的
的关注点,例如,在模型视图 - 控制器(MVC)[30]模式中



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



均匀性不同的方法安装不同的观察者
减少代码均匀性。



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



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



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



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


I've noticed that my dependency injected, observer-pattern-heavy code (using Guava's EventBus) is often significantly more difficult to debug than code I've written in the past without these features. Particularly when trying to determine when and why observer code is being called.

Martin Oderski and friends wrote a lengthy paper with an especially alluring title, "Deprecating the Observer Pattern" and I have not yet made the time to read it.

I'd like to know what is so wrong with the observer pattern and so much better about the (proposed or other) alternatives to lead such bright people to write this paper.

As a start, I did find one (entertaining) critique of the paper here.

解决方案

Quoting directly from the paper:

To illustrate the precise problems of the observer pattern, we start with a simple and ubiquitous example: mouse dragging. The following example traces the movements of the mouse during a drag operation in a Path object and displays it on the screen. To keep things simple, we use Scala closures as observers.

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)
}

The above example, and as we will argue the observer pattern as defined in [25] in general, violates an impressive line-up of important software engineering principles:

Side-effects Observers promote side-effects. Since observers are stateless, we often need several of them to simulate a state machine as in the drag example. We have to save the state where it is accessible to all involved observers such as in the variable path above.

Encapsulation As the state variable path escapes the scope of the observers, the observer pattern breaks encapsulation.

Composability Multiple observers form a loose collection of objects that deal with a single concern (or multiple, see next point). Since multiple observers are installed at different points at different times, we can’t, for instance, easily dispose them altogether.

Separation of concerns The above observers not only trace the mouse path but also call a drawing command, or more generally, include two different concerns in the same code location. It is often preferable to separate the concerns of constructing the path and displaying it, e.g., as in the model-view-controller (MVC) [30] pattern.

Scalablity We could achieve a separation of concerns in our example by creating a class for paths that itself publishes events when the path changes. Unfortunately, there is no guarantee for data consistency in the observer pattern. Let us suppose we would create another event publishing object that depends on changes in our original path, e.g., a rectangle that represents the bounds of our path. Also consider an observer listening to changes in both the path and its bounds in order to draw a framed path. This observer would manually need to determine whether the bounds are already updated and, if not, defer the drawing operation. Otherwise the user could observe a frame on the screen that has the wrong size (a glitch).

Uniformity Different methods to install different observers decrease code uniformity.

Abstraction There is a low level of abstraction in the example. It relies on a heavyweight interface of a control class that provides more than just specific methods to install mouse event observers. Therefore, we cannot abstract over the precise event sources. For instance, we could let the user abort a drag operation by hitting the escape key or use a different pointer device such as a touch screen or graphics tablet.

Resource management An observer’s life-time needs to be managed by clients. Because of performance reasons, we want to observe mouse move events only during a drag operation. Therefore, we need to explicitly install and uninstall the mouse move observer and we need to remember the point of installation (control above).

Semantic distance Ultimately, the example is hard to understand because the control flow is inverted which results in too much boilerplate code that increases the semantic distance between the programmers intention and the actual code.

[25] E. Gamma, R. Helm, R. Johnson, and J. Vlissides. Design patterns: elements of reusable object-oriented software. Addison-Wesley Longman Publishing Co., Inc., Boston, MA, USA, 1995. ISBN 0-201-63361-2.

这篇关于为什么不应该忽略观察员模式?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆