开闭原则和依赖倒置原则有什么区别?

19

DIP原则的表述如下:

  • 高层模块不应该依赖于低层模块,两者都应该依赖于抽象。
  • 抽象不应该依赖于具体实现细节,具体实现细节应该依赖于抽象。

OCP原则的表述如下:

软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。

我认为如果我们满足了DIP原则,也就涵盖了OCP原则,那么为什么要将这两个原则分开呢?

5个回答

16
Uncle Bob Martin将开闭原则(OCP)和依赖倒置原则(DIP)作为SOLID原则中的两个进行了普及,他自己指出,DIP源自于OCP和Liskov替换原则的应用:
“在这篇专栏文章中,我们讨论了OCP和LSP的结构含义。严格遵循这些原则所得到的结构可以概括为一个原则。我称其为“依赖倒置原则”(DIP)。 ”——Robert C. Martin,《Engineering Notebook, C++ Report》1996年。
因此,你说每个DIP实例都是OCP实例是正确的,但OCP更加通用。 这里有一个最近遇到的只使用OCP而不使用DIP的用例。 许多Web框架有信号的概念,其中一个动作触发一个信号。 发送信号的对象完全不知道已注册信号的监听者。 每次要向信号添加更多监听器时,可以这样做而无需修改发送方。
这明显展示了OCP(“封闭修改,对扩展开放”),但不是DIP,因为发送方不依赖任何内容,因此谈论它是否依赖于更抽象或更少的东西是没有意义的。
更普遍地说,观察者模式(GoF模式之一)描述了如何遵守OCP但不涉及DIP。有趣的是,浏览GoF书籍并查看哪些与OCP有关,以及其中有多少与DIP无关。

8

我认为遵循DIP原则可以更容易地符合OCP原则,但是一个并不能保证另一个。

例如,我可以创建一个类,其中有一个方法,该方法需要一个名为base的参数。如果base是一个抽象类,那么我就遵循了DIP原则,因为我已经将依赖倒置给了调用者。然而,如果该方法中的代码执行了以下操作:

if (base is derived)
    (derived)base.DoSomethingSpecificToDerived;
elsif (base is evenMoreDerived)
    (evenMoreDerived)base.DoSomethingSpecificToEvenMoreDerived;

如果每次添加新的派生类都需要修改它,那么它就不符合OCP标准。

这只是一个非常牵强的例子,但你明白我的意思。


4
但是在你的例子中,高层模块(你的代码所在的模块)依赖于低层实现(派生、更多派生),因此我认为你的示例也违反了依赖反转原则。 - Masoud
没错。然而,我对DIP的理解是将依赖关系的控制反转到更高的级别,而这个类就做到了。正如我所说,这只是一个非常牵强的例子。 - David Osborne

6
DIP(依赖反转原则)告诉你如何组织依赖关系,但它并不告诉你何时完成特定接口。简单来说,OCP(开放封闭原则)的意义在于拥有完整但极简的接口。换句话说,它告诉你何时完成接口,但不告诉你如何实现它。从某种意义上讲,DIP和OCP是正交的。
那么,为什么我们要将这两个原则分开呢?就设计模式和命名原则而言,几乎所有的模式和原则都有一个共同点: 1. 找到变化并封装(隐藏)它。 2. 优先使用聚合而非继承。 3. 设计到接口。
即使命名的模式和原则在某种意义上部分重叠,它们比以上三个通用原则更具体(在更具体的情况下告诉您一些东西)。

3
不太清楚“当你完成一个接口时”和“如何实现这一点”的含义。 - Oleksandr Novik

2

@CS的回答很好。总结一下:

  • DIP是OCP的扩展,因此
  • 当我们满足DIP时,通常也会满足OCP。
  • 反之则不成立,我们可以构想出符合OCP但违反DIP的情况。这里有一个(Java)例子。
public abstract class MyClass {
    DependencyOne d1;
    DependencyTwo d2;

    MyClass() {
        d1 = new DependencyOne();
        d2 = new DependencyTwo();
    }
}

OCP得以满足,因为我们可以扩展类。DIP被违反了,因为我们直接实例化了依赖项。
现在的挑战是,我们能否想到一个符合DIP的、违反OCP的例子。我能想到的最好的例子是注释。在Java中,我们使用@Deprecated注释来标记代码,该代码开放于修改,从而违反了OCP。同时,这段代码在抽象和依赖方面可能是完全符合DIP的。某些库使用一个@Beta注释来达到类似的效果。
我无法想象一个既符合DIP又不可扩展的例子,除了没有依赖关系的类的零元示例,这并不是很有趣。我认为DIP意味着开放性扩展。然而,在某些边缘情况下,DIP可能并不意味着关闭修改。

0

OCP使得依赖类易于使用。OCP通过将旧实现与新版本解耦,实现接口的异步使用。即使面临其他目的的变化,它也允许依赖它的事物继续依赖它。这样一来,一个类永远不必关心谁在调用它。

DIP有几个作用。它使得依赖外部类变得容易。依赖注入通过鼓励创建职责与消费职责的分离,从而实现了依赖项的替换。模式规定,应该在外部提供要使用的外部依赖项,而不是创建它。最终,这鼓励编写幂等代码(不改变外部状态的代码)。幂等代码很好,因为可以验证它只做了立即可见的事情。它没有外部副作用。它非常易于测试、理解和阅读。


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