直接引用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。