观察者模式是否违反单一职责原则?

4
如果一个应用程序使用观察者设计模式,其中有一个名为subject的类具有以下职责:
1) 管理和通知观察者(即提供注册和注销功能,并调用所有观察者通知函数);
2) 其原始职责(即在成为subject之前该类所做的任何事情)。
这个类是否违反了单一职责原则?它显然有多个职责,但在阅读SRP时,我对“更改原因”感到困惑。这种更改是在设计时间还是运行时间进行的?
4个回答

2
不,观察者设计模式不违反单一职责原则(SRP)。
什么是职责?
“职责表示对象提供某种行为的义务。”(《面向对象分析与设计》,格雷迪·布奇等人,第600页)
但是SRP将职责定义为更改的原因。SRP指出,一个类应该只有一个职责(更改的原因)。
这对应于GoF原则——封装可能发生更改的事物——这是许多GoF设计模式的主题。例如,策略模式在单独的策略类中封装了一个算法(可能会更改)。
观察者模式并不涉及封装可能发生更改的事物。它描述了一种定义交互对象之间的一对多依赖关系的方法,而不使对象紧密耦合。
有关进一步讨论,请参见GoF设计模式内存,以学习面向对象的设计和编程:http://w3sdesign.com

1
回答自己的问题,因为bav的答案虽然链接到了一个很好的资源,但并没有回答问题。(但是看视频让我理解了,这使我能够回答问题!)
不,这是对单一职责原则(SRP)的误解。
SRP中的“责任”指的是可能发起更改请求的“客户角色”。例如,如果一个类Employee具有calculatePay()和displayEmployee()方法,可以合理地认为它违反了SRP,因为calculatePay将“属于”公司会计师,而displayEmployee将属于报告文员。这意味着两个拥有不同角色的人可以请求对类进行更改。
观察者模式不会增加新的责任(至少不是SRP的责任),因为没有客户角色会关心这个类向其观察者发布更改。

1
在我看来,观察者模式确实违反了单一职责原则(有时候)。
想象一下以下类,是MVC应用程序的一部分(Java伪代码):
class Model {
    void setDateOfBirth(Date);
    Date getDateOfBirth();
    int getAge(); // calculated from date of birth
    void registerObserver(Observer);
    void unregisterObserver(Observer);
    void notifyObservers();
}

前三种方法明显涉及管理应用程序数据,而后三种方法则没有。
单一职责原则(SRP)指出,一个类应该只有一个改变的原因[1]。我可以想到多个更改观察者方法的原因,其中应用程序数据的管理不应受影响。以下是一些例子:
  • 我想要并行通知所有观察者以提高性能。
  • 我想通过某个消息总线通知观察者,因为新视图在其他进程中运行。
  • 我想并行注册和注销观察者,因此需要一种其他列表实现(线程安全的实现)。
(有人可能会争辩说,所有这些事情都可以隐藏在“Observer”本身中。但是,注册一个允许并发注册其他观察者的观察者似乎也不正确,我认为。)
顺便说一下,观察者模式还违反了DRY(不要重复自己)原则,因为您需要一遍又一遍地实现注册、注销和通知机制(即对所有观察者进行循环)。
但是:
这两个责任应该分开吗?这取决于应用程序的变化方式。如果应用程序没有以导致这两个职责在不同时间发生变化的方式发生变化,则无需将它们分开。事实上,将它们分开会显得过于复杂。所以你必须自己决定并平衡SRP和DRY与不必要的复杂性。
[1] 罗伯特·马丁(又称Bob大叔)的书《面向对象设计原则》以及他的书《敏捷软件开发:原则、模式与实践》第8章。 The Principles of OOD

我觉得你的回答很有趣,我相信按顺序/并行处理事情可能是一种责任(例如来自审核性能的演员)。问题的关键是要决定性能是否将成为项目这个领域的紧张源泉(现在或将来可能有多少订阅者)。这更多地涉及到开放/封闭原则。但我想知道是否可以简单地注入循环观察者的行为(将观察者列表作为参数传递给此策略)。这不会很昂贵。 - geoffrey

0

是的,它确实可以。 但是....

让我们回到设计模式背后的大思想,那就是帮助开发人员更轻松地处理未来可能出现的变化。

你为设计设置的粒度级别取决于你自己,通往黄金中间的道路应该由你的常识、经验和应用程序的规模来指导。

如果预见到仓库分布的变化,你可以创建一个中介者(发布-订阅),负责将更改分发给观察者。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接