Answers:
我之前写过关于开放式封闭原则(OCP)与Liskov的替代原则(LSP)的答案,这两个原则相互关联很多,但在概念上仍然有所不同,有些人为设计的例子中有一个但没有另一个。由于这个答案,我将只简要介绍一下OCP,然后深入探讨DIP,以及使该滴答作响的原因。
让我们先通过首先解释不同的原理,来讨论OCP与依赖反转原理(DIP)的关系和区别。
阅读Bob叔叔的OOD原理,您会发现DIP声明以下内容:
取决于抽象,而不取决于混凝土。
Java中的抽象可以简单地通过interface
和abstract
关键字来实现,这意味着您对代码必须遵循的某些软件实体具有“合同”。某些编程语言没有工具来明确设置要遵循的代码的行为,因此必须以更加手动的方式遵循抽象,而不是让编译器帮助您执行合同。例如,在C ++中,您具有使用虚方法的类,而动态编程语言(例如Javascript)则必须确保以相同的方式使用对象(尽管在Javascript中,TypeScript中对此进行了扩展,添加了类型系统来帮助您)编写经编译器验证的合同)。
该名称包含“反转”一词,因为传统上(您知道在编程的黑暗时代中)您编写的软件结构具有较高级别的模块,具体取决于较低级别的模块。例如,ButtonAtKitchen
对于KitchenLamp1
和具有处理输入是有意义的KitchenLamp2
。不幸的是,这使得软件比需要的更加具体,对象图如下所示:
因此,当您使软件更加通用时,可以添加“合同”。注意对象图中的箭头如何“反转”方向。那个厨房灯现在依赖于Button
。换句话说,细节现在依赖于抽象,而不是相反。
因此,我们对DIP有更一般的定义,在Bob叔叔的DIP原始文章中也有详细介绍。
答:高级模块不应依赖于低级模块。两者都应依赖抽象。B.抽象不应依赖细节。细节应取决于抽象。
继续遵循Bob叔叔的原则,您会发现OCP声明以下内容:
您应该能够扩展类的行为,而无需对其进行修改。
实现此目标的一个示例是采用策略模式,其中关闭一个Context
类以进行修改(即,您根本无法更改其内部代码),但也可以通过其协作依赖性(例如,策略类)对其进行扩展。
从更一般的意义上讲,任何模块都可以通过其扩展点进行扩展。
不,不是。
尽管他们都在讨论抽象,但是它们在概念上有所不同。两种原则都针对不同的上下文,OCP在一个特定模块上,DIP在多个模块上。您可以同时使用大多数“四人行”设计模式同时实现这两个目标,但是仍然可以避开这条路。
在上面提到的DIP示例中,对于按钮和厨房灯,没有一个厨房灯是可扩展的(目前也没有任何要求说明它们是必需的)。该设计打破了OCP,但遵循了DIP。
一个相反的(也是人为的)示例是厨房灯是可扩展的(扩展点类似于LampShade
),但是按钮仍取决于灯。它正在打破DIP,但遵循OCP。
实际上,这是您经常会在生产代码中看到的东西,它的某些部分可能会违反原则。在较大的软件系统中(即,比上面的示例大的任何东西),您可能会违反一个原则,但通常会保留另一个原则,因为您需要保持代码简单。在我看来,这对于小型且独立的模块来说是可以的,因为它们与“单一责任原则”(SRP)有关。
一旦某个模块变得复杂,尽管您很可能需要牢记所有原则,然后将其重新设计或重构为某种众所周知的模式。