2012-07-23 109 views
32

我注意到,我的依赖项被注入了,观察者模式重的代码(使用Guava的EventBus)通常比我没有写过这些特性的代码更难以调试。特别是在试图确定何时以及为什么要调用观察者代码时。为什么观察者模式应该被弃用?

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

我想知道什么是错与观察者模式和这么多的(建议或其他方式)的替代领导这样聪明的人写的文章好。

作为一个开始,我确实发现论文here之一(娱乐)的批评。

+3

也在这里讨论过...... http://lambda-the-ultimate.org/node/4028 – 2012-07-30 21:33:03

回答

28

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我们可以通过为自己发布 事件时的路径改变路径,创建一个类实现我们 例如关注点分离。不幸的是,在观察者模式中没有数据一致性的保证 。 让我们假设我们将创建另一个事件,发布 对象,该对象取决于我们原始路径中的更改,例如,表示我们路径边界的矩形,即 。另外, 考虑观察者聆听路径及其边界中的更改以绘制框架路径。这个观察者将手动确定 范围是否已经更新,如果不是,则延迟图形操作 。否则,用户可能会在屏幕上观察 的大小错误(毛刺)的屏幕。

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

抽象该示例中抽象层次较低。 它依赖于控件的一个重量级界面,它提供的不仅仅是特定的方法来安装 鼠标事件观察者。因此,我们无法通过精确的事件来源摘要 。例如,我们 可以通过点击转义键 或使用不同的指针设备(例如触摸屏或图形输入板)来中止拖动操作。

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

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

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

6

我相信观察者模式具有标准的缺点,与解耦的东西。该主题与观察者分离,但不能只看其源代码并找出谁观察它。硬编码依赖通常更易于阅读和思考,但它们很难修改和重用。这是一个折衷。

至于纸,它没有解决Observer模式本身,而是它的一个特殊用法。特别是:每个观察对象的多个无状态Observer对象。这有独立观察家需要彼此同步(“自观察员无状态的,我们往往需要几个人来模拟 状态机作为阻力例如明显的缺陷。我们必须拯救 的状态下它所有相关的观察者都可以访问 ,例如上面的可变路径“)

上述缺点是特定于这种用法,而不是Observer模式本身。你还可创建一个实现所有OnThisOnThatOnWhatever方法,摆脱在许多无状态的对象模拟状态机的问题单(有状态!)观测对象。

+0

但是在例子* do *中的观察者明显有副作用,就像你在报价中提到的一样。 (请参阅“路径”def的所有操作)。 – 2012-07-24 13:46:26

6

我会简短介绍一下,因为我是这个主题的新手(并没有阅读那篇特定的文章)。

观察者模式在直觉上是错误的:要观察的对象知道谁在观察(主题<> - 观察者)。 这是违反现实生活(在基于事件的场景中)。如果我尖叫,我不知道谁在听;如果闪电,击中地板...闪电不知道有一个地板,直到它击中!只有观察员知道他们能观察到什么。

当这种事情发生的时候,软件就会变成一团乱麻 - 因为它是与我们的思维方式相对立的。 这就好像和对象知道其他对象可以调用他的方法一样。

国际海事组织的一个层如“环境”是负责采取事件并通知受影响的人。 (或混合该事件和该事件的发生器)

事件源(主题)生成环境事件。环境将事件传递给观察者。观察员可以注册那些影响他的事件或实际上在环境中定义的事件。这两种可能性都是有道理的(但我想简短一点)。

在我的理解观察者模式放在一起环境&主题。

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

+0

这里http://www.marco.panizza.name/dispenseTM/slides/exerc/eventNotifier/eventNotifier.html 我发现它基本上是说同样的图: -Publisher(主题) -Subscriber(对象) - 事件服务(环境) (1998) 毕竟“常识”导致它和这种或那种方式,我们在编程时实现这些中间层。问题出现在Pattern激发或误导时。 – 2012-12-13 11:14:08

+0

受试者不知道他们的观察者(也许只能通过基类)。设计模式书也提到了变更管理者。这与你的环境有些相似。 Imho Observer模式非常适合(独立)GUI元素的通知,但该模式也有一些严重的缺陷:虚假通知,而主体或业务层不处于一致状态;在通知更新期间,观察者更改主题。 – gast128 2015-02-20 11:48:18

+0

这里有几点,主体不知道有关观察者的任何事情,我们可以通过抽象接口来实现这一点。 – 2018-03-04 20:33:53