我注意到我的依赖注入,观察者模式繁重的代码(使用Guava的EventBus)通常比我过去编写的没有这些功能的代码更难调试。特别是在尝试确定调用观察者代码的时间和原因时。
马丁·奥德斯基和朋友写了一篇冗长的论文,题目特别诱人,"Deprecating the Observer Pattern",我还没有时间阅读它。
我想知道观察者模式的错误是什么,以及更好地引导这些聪明人写这篇论文的(建议的或其他的)替代方案。
首先,我确实找到了一篇(有趣的)评论文章here。
答案 0 :(得分:33)
直接从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]模式中那样。
Scalablity 我们可以实现我们的关注点分离 例如,为自己发布的路径创建一个类 路径改变时的事件。不幸的是,这儿没有 保证观察者模式中的数据一致性。 让我们假设我们将创建另一个事件发布 依赖于原始路径变化的对象,例如, 一个表示路径边界的矩形。也 考虑观察者听取两者的变化 路径及其边界,以绘制框架路径。这个 观察者会手动确定是否需要 边界已经更新,如果没有,则延迟绘图 操作。否则,用户可以观察到帧 屏幕尺寸错误(故障)。
统一性安装不同观察者的不同方法 降低代码一致性。
抽象示例中的抽象级别较低。 它依赖于控件的重量级接口 提供不仅仅是要安装的特定方法的类 鼠标事件观察员。因此,我们不能抽象 在精确的事件来源。例如,我们 可以让用户通过点击转义来中止拖动操作 键或使用不同的指针设备,如触摸 屏幕或图形输入板。
资源管理观察者的生命周期需要 由客户管理。由于性能原因, 我们只想在a期间观察鼠标移动事件 拖动操作。因此,我们需要明确安装 并卸载鼠标移动观察器,我们需要 记住安装点(上面的控制)。
语义距离最终,这个例子很难理解 因为控制流是反转的 在太多的样板代码中增加了语义 程序员意图与之间的距离 实际的代码。
[25] E. Gamma,R。Helm,R。Johnson和J. Vlissides。设计 模式:可重用的面向对象软件的元素。 Addison-Wesley Longman Publishing Co.,Inc.,Boston,MA, 美国,1995年。国际标准书号0-201-63361-2。
答案 1 :(得分:9)
我认为观察者模式具有解耦的标准缺点。主题与观察者分离,但你不能只看它的源代码并找出谁观察它。硬编码的依赖关系通常更容易阅读和思考,但它们更难以修改和重用。这是一个权衡。
至于论文,它没有解决观察者模式本身,而是它的特定用法。特别是:每个被观察的单个对象有多个无状态Observer对象。这有一个明显的缺点,即单独的观察者需要彼此同步(“因为观察者是无国籍的,我们经常需要他们中的几个来模拟 拖动示例中的状态机。我们要保存 所有相关观察员都可以访问的状态 例如在上面的变量路径中。“)
上述缺点特定于此类用法,而不是Observer模式本身。您还可以创建一个实现所有OnThis
,OnThat
,OnWhatever
方法的单个(有状态!)观察器对象,并解决跨多个无状态对象模拟状态机的问题
答案 2 :(得分:6)
我会简短,因为我是这个主题的新手(并没有读过那篇特定文章)。
观察者模式直观错误:要观察的对象知道谁在观察(主体<> - 观察者)。 这与现实生活相反(在基于事件的情景中)。如果我尖叫,我不知道谁在听;如果闪电,击中地板......闪电不知道有地板,直到它击中!只有观察者知道他们能观察到什么。
当这种事情发生时,软件就会变得混乱 - 因为这与我们的思维方式相反。 好像和对象知道其他对象可以称之为他的方法。
IMO“环境”等层负责接收事件并通知受影响的人。 (或混合事件和该事件的生成器)
Event-Source(Subject)为环境生成事件。环境将事件传递给Observer。观察者可以注册影响他的事件,或者它实际上是在环境中定义的。两种可能性都有意义(但我希望简短)。
根据我的理解,观察者模式将环境与环境放在一起。主题。
PS。讨厌提出段落抽象的想法! :P