依赖倒置设计选择题

Posted

技术标签:

【中文标题】依赖倒置设计选择题【英文标题】:Dependency inversion design choice question 【发布时间】:2021-01-02 16:08:03 【问题描述】:

我目前正在阅读有关依赖倒置原则的其他优秀教程

https://www.baeldung.com/java-dependency-inversion-principle

尽管思考了相当长的时间,但有些东西我还是无法解释

DIP 定义的相关部分:“高层模块不应该依赖低层模块。两者都应该依赖于抽象。”

在第3.1点“Design Choices and the DIP”作者通过一个例子介绍了原理,其中StringProcessor类使用了StringReaderStringWriter组件并给出了多种设计选择wrt 使用接口/类和包。我的问题是选择 2,即

"StringReaderStringWriter 是与实现一起放在同一个包中的接口。StringProcessor 现在依赖于抽象,但低级组件不依赖。我们还没有实现还没有依赖”

StringProcessor“高级组件”,它依赖于 “抽象”StringReaderStringWriter 接口,从而从一个实现 DIP 定义侧面,这很清楚。现在给出第一句中提到的整篇文章“实现”中使用的术语,例如ConcreteStringReaderConcreteStringWriter 类将是这里的 “低级组件”,而我只是无法理解它们如何不依赖于 “抽象” em> 即在它们实现的接口上,无论包含什么包

显然,从代码组织的角度来看,将实现与其接口放在同一个包中可能不是最好的,但目前我无法理解这如何违反上述逐字 DIP 定义,具体取决于抽象。

也许对这个主题有更深入理论知识的人可以在这里帮助我

【问题讨论】:

“也许有人有更深的理论知识......”我认为这是对 SOLID 的普遍误解。有没有深入的了解可以解锁。整件事都是一堆废话。 我不同意,有点。每个 SOLID 原则都很好,其中一些对于非常广泛地理解和应用是必不可少的。它们是必要的,但我认为它们还不够,它们也不是魔法。与设计模式一样,它们更像是一种方便的方式来引用优秀开发人员应该通过经验学到的东西,但不是向新开发人员介绍和灌输这些想法的好方法。 @erickson 是的,说得很好。我认为我们大致同意,除了一件事:“每个 SOLID 原则都是好的”。 IMO OCP 是一个非常有害、不连贯的想法。其余的核心都还可以,但它们的构造很迟钝。 【参考方案1】:

隐含的一般概念是一个包相当于一个抽象级别。因此,在第 3.1.2 节中,具体实现“拥有”它们的抽象,因为它们位于同一个包中;因为在任何发布包的地方,这些实现都是顺其自然的。共享包的类之间的耦合在一定程度上体现在语法上,即使在 Java 8 中也是如此。例如,import 语句不是必需的,具有默认访问修饰符的类和方法是可见的。

不过,鉴于 JPMS 功能,更容易发现第 3.1.2 节中的缺陷。模块是在包级别定义的,将包是单个抽象级别的概念形式化。就 DIP 而言,依赖关系也在包级别考虑。如果一个包包含具体的实现,它被认为是低级,并且不应该有传入的依赖。

深入探讨该主题的整本书是Java Application Architecture: Modularity Patterns。

【讨论】:

谢谢。经典的包示例似乎解释了实现“拥有”而不是“依赖”它们的抽象,从而回答了我的问题。然而 JPMS 示例让我有些困惑,因为我倾向于理解这里的 DIP 违规实际上是包含高级组件的模块依赖于包含抽象和低级组件的另一个模块,从而成为低级模块 - 矛盾声明“(高级组件)现在依赖于抽象”的作者。我说对了吗? TBH,我不确定作者在 3.1.2 中的意思,“...低级组件没有。”我没有认为它们的抽象是否存在于同一个包中会对低级组件本身产生影响;但它对抽象的其他消费者,尤其是高级组件有很大的不同。我认为低级组件会污染或感染它们的包(带有实现细节)。为了避免传播感染(通过传递依赖),DIP 禁止依赖低级包中的任何内容。 JPMS 不会更改 DIP。它只明确了高级与低级包的边界,因此 DIP 违规将显示在 module-info.java 文件中。 我明白了。鉴于此,3.1.2 “(高级组件)现在依赖于抽象” 中提到的其他声明似乎也无效;鉴于它实际上取决于包含实现细节的受感染包/模块? “感染”包包含抽象和实现细节,所以你可以说高级组件依赖于两者;但具体违反 DIP 的是实现细节,因为包中的所有内容都是耦合的。【参考方案2】:

"StringReader和StringWriter是放在同一个接口 与实现一起打包。 StringProcessor 现在依赖于 抽象,但低级组件没有。我们没有达到 依赖倒置”

虽然它确实不是 DIP,但 IMO 的解释是错误的。问题是作者未能识别类/组件和层/包/模块之间的区别。 DIP是一个只适用于模块间关系的原理,显然不适用于单个模块的情况。

至于第四点,

同样,第 4 项是一个更加解耦的 DIP 实现。在这个 模式的变体,既不是高级组件也不是 低级的拥有抽象的所有权。

我们在这里需要非常小心,因为“所有权”不仅仅是“在同一个包中”。

例如这将是 DIP 违规

【讨论】:

在图片中,我认为JPMS(或OSGi)可以在spiimpl包之间定义一个明确的边界,这就是为什么包(而不是jar)被认为是基本单元的原因模块化(因此是依赖倒置的单元)。可以发布一个包含两者但只公开一个的 jar。 @jaco0646 是的,但我想在这里重点关注的是my_project 不控制spi,因此取决于可能由低级库设计驱动的更改决定。这在概念上也违反了 DIP IMO。我认为接口需要在高级模块中定义,在高级模块的实现者(项目/公司)拥有的包中,或者作为全球标准拥有或部署。

以上是关于依赖倒置设计选择题的主要内容,如果未能解决你的问题,请参考以下文章

手撸golang 架构设计原则 依赖倒置原则

七大设计原则之依赖倒置原则应用

依赖倒置原则——面向对象设计原则

Java设计原则—依赖倒置原则(转)

必知必会的设计原则——依赖倒置原则

六大设计原则DIP依赖倒置原则