为什么“保护免受更改”暗示依赖的方向?

时间:2018-12-26 14:37:51

标签: oop architecture solid-principles dependency-inversion

关于我的一些背景知识:我是一名自学成才的程序员,六年前我加入MegaCorp(TM)时,他就开始使用Python并学习Java。拥有数学学位,我对算法和批判性思维非常扎实(没有双关语),但是我经常发现自己与同龄人在计算机科学中学到的数据结构,设计或其他CompSci基础知识有关课程。

为此,我向团队中的一名高级工程师提出了书本建议以帮助填补我的空白,他建议Clean Architecture

我已经走了三分之一,并且真的被建议的主要推动因素之一所迷惑。鲍勃叔叔提出了许多想法和原则(包括我以前听说过的SOLID原则,尽管我仍然会掌握《里斯科夫换人原则》),旨在“保护”其中的一部分从需求到变化的系统。有几个示例,但最清楚的是第73页:

  

如果应保护组件A免受组件B的更改,则   组件B应该取决于组件A。

(我应该注意,在我没有看到任何实际定义的情况下,我认为“组件”等同于Java包,尽管我认为如果“组件”是一项单独的服务-两者都应向用户提供稳定的可用接口,无论是本地调用还是通过网络调用)

此声明没有任何证据,对我来说也不是不言自明的。考虑组件(包)ClassA中的类ComponentA的情况,该类在组件DoStuffReturn doStuff(DoStuffInput input, String someOtherArg)的{​​{1}}中调用ClassB-并且其中调用是通过直接依赖关系,或通过对接口 in ComponentB中的依赖关系(如Clean Architecture建议的那样,ComponentB not

  • 如果B中的更改是功能更改,而不是签名更改(即-对于相同的ComponentADoStuffInput输入,将返回不同的String),则没有更改在A中应该是必需的:
    • DoStuffReturnClassA的调用仍然有效(相同的参数和返回类型)
    • ClassB.doStuff的单元测试(应使用模拟的ClassA进行)仍应通过
    • 任何测试ClassBClassA合作方式的功能/集成测试都需要更新其期望,但这并不是ClassB的变化(除非这些测试是在{em> ComponentA中-我通常在ComponentA包中看到了它们的外部化,但我想它们也可以放在同一位置,但这似乎不是书中所建议的-似乎是在谈论更改代码,而不是测试)
  • 如果B中的更改是对ComponentAIntegrationTests以外的方法的签名更改,则A将不需要任何更改
  • 如果B中的更改是对doStuff的签名更改,则A 将需要更改-但是如果接口也位于A中,情况也会如此。
  • li>

(请注意,在Clean Architecture倡导的设置下,其中提供类的接口位于使用方的组件(A)中,只有第一种情况会构成B的更改-因此,这是我们真正需要的唯一一种与之相关)

我想念什么?如果ComponentA依赖于ComponentB,那么在什么情况下ComponentB中的类更改需要ComponentA中的更改?

(请注意,我不是在提倡使用接口-它们有许多其他好处,尤其是允许在合同的“双方”同时进行开发,并允许将合同交换出去)接口的各种实现)

2 个答案:

答案 0 :(得分:1)

为了解释如何通过反转依赖方向来保护一个组件免受另一个组件的更改,让我们首先定义 component dependency 。在Clean Architecture的上下文中,组件是jar文件,而依赖关系是类之间的链接。

  

组件是部署的单元。它们是可以作为系统一部分部署的最小实体。在Java中,它们是jar文件。 -第96页

  

首先要注意的是,所有依赖项都是源代码依赖项。从类A指向类B的箭头表示类A的源代码提及类B的名称,而类B则未提及类A。-第72页

然后问题变成了,我们要保护组件(罐子)免受哪些更改?答案是:重新编译,重新部署和传递依赖。

如果组件A依赖于组件B,则B中的任何更改(即使该更改不影响A所使用的API)都需要{{1 }}重新编译。此外,如果A中的更改添加,删除或修改了B的依赖项,那么B必须调和新的传递性依赖项,因此更改会传播。

  

这种依赖性意味着对[A]的源代码所做的更改将迫使[B]重新编译和重新部署,即使实际上它关心的任何内容都没有改变。 -第84页

  

传递依赖关系违反了通用原则,即软件实体不应依赖于它们不直接使用的事物。 -第75页

我在这里混合来自多个SOLID原理的元素;但是它们在处理变更方面具有潜在的共性。

答案 1 :(得分:0)

就我的背景而言,我拥有计算机科学学位,并且拥有20多年的专业经验。

首先是第一件事。在该领域中,没有任何东西定义得很好(好吧,一些与数学有关的东西)。甚至是基本的东西,例如面向对象本身,或者封装,单一职责,以及更复杂的东西,例如域驱动设计,REST等。在所有方面都存在着截然不同的意见。有时候,流行的解释或这些东西是最糟糕的选择。

我要说的是,即使面对压倒性的权威,也要保持怀疑态度。对事物提出疑问,并始终尝试找到支持某物的有效理由。不仅“这样更容易维护”,这还只是一个假设,而不是证据。

我碰巧认为Bob叔叔不是有关面向对象的理想信息来源。 Here是对我早些时候写的“清洁体系结构”(不是本书,这个主意)的详细评论。

回到您的问题。引号本身基本上说依赖性的方向与变化的扩散背道而驰(here也是我的一篇带有一些漂亮图片的文章)。这并不意味着(也不应该)总是 改变通过依赖流回,但是“知识”确实如此。总是。有时,更改不需要外观,例如签名更改。容量,性能的更改有时会破坏更改,但无法在签名中直接看到。方法的含义可能也会更改而没有任何明显的迹象。

所以,您是对的,内部更改可能根本不会导致任何其他更改。但是避免潜在发生变化的唯一方法是一开始就不依赖于该事物。

第二个最好的方法是拥有良好的抽象和封装,不幸的是,“干净的体系结构”并不能很好地做到这一点。 Bob叔叔倡导的将贫血对象和纯数据用作接口会破坏组件可能具有的任何抽象或封装。