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.
我相信观察者模式具有与解耦事物相关的标准缺点.主题与观察者分离,但你不能只看它的源代码并找出谁观察它.硬编码的依赖关系通常更容易阅读和思考,但它们更难以修改和重用.这是一个权衡.
至于论文,它没有解决观察者模式本身,而是它的特定用法.特别是:每个被观察的单个对象有多个无状态Observer对象.这有明显的缺点,即单独的观察者需要彼此同步("由于观察者是无状态的,我们经常需要其中几个来模拟状态机,就像在拖动示例中一样.我们必须保存可以访问的状态.所有涉及的观察者,例如在上面的可变路径中. ")
上述缺点特定于这种用法,而不是Observer模式本身.你还可创建一个实现所有单(有状态!)观测对象OnThis,OnThat,OnWhatever方法和摆脱在许多无状态的对象模拟状态机的问题.
我会简短,因为我是这个主题的新手(并没有阅读那篇特定的文章).
观察者模式直观错误:要观察的对象知道谁在观察(主体<> - 观察者).这与现实生活相反(在基于事件的情景中).如果我尖叫,我不知道谁在听; 如果闪电,击中地板......闪电不知道有地板,直到它击中!只有观察者知道他们能观察到什么.
当这种事情发生时,软件就会变得混乱 - 因为这与我们的思维方式相对立.好像和对象知道其他对象可以称之为他的方法.
IMO是一个"环境"层,负责接收事件并通知受影响的人.(或混合事件和该事件的生成器)
Event-Source(Subject)为环境生成事件.环境将事件传递给Observer.观察者可以注册影响他的事件,或者它实际上是在环境中定义的.两种可能性都有意义(但我希望简短).
在我的理解中,观察者模式将环境和主题放在一起.
PS.讨厌提出段落抽象的想法!:P