2

关于我的一点背景:我是一名自学成才的程序员,6 年前加入 MegaCorp(TM) 时,他开始使用 Python 并学习了 Java。拥有数学学位,我在算法和批判性思维方面相当扎实(不是双关语),但我经常发现自己在数据结构、设计或其他 CompSci 基础知识方面存在差距,而我的同行在他们的计算机科学中学到了这些基础知识培训班。

为此,我向团队中的一位高级工程师请教了一本书,以帮助填补我的空白,他建议使用Clean Architecture

我大约完成了三分之一,并且对这些建议的主要激励因素之一感到非常困惑。鲍勃叔叔提出了许多想法和原则(包括我以前听说过的SOLID原则,尽管我仍在掌握 Liskov 替换原则)旨在“保护”系统的某些部分免受要求改变。有几个例子,但最清楚的是第 73 页:

如果组件 A 应该被保护免受组件 B 的更改,那么组件 B 应该依赖于组件 A。

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

这种说法没有提供任何证据,对我来说也不是不言而喻的。考虑ClassA组件(包)中的类在组件中ComponentA调用DoStuffReturn doStuff(DoStuffInput input, String someOtherArg)的情况- 并且其中调用是通过直接依赖关系或通过对接口的依赖关系不是in,正如 Clean Architecture 所建议的那样)ClassBComponentB ComponentBComponentA

  • 如果 B 中的更改是功能更改,而不是签名更改(即 - 对于相同的DoStuffInput输入StringDoStuffReturn返回不同的),则 A 中不需要更改:
    • ClassA的调用ClassB.doStuff保持有效(相同的参数和返回类型)
    • ClassA的单元测试(应该使用 mocked ClassBs)应该仍然通过
    • 任何测试如何ClassAClassB协作的功能/集成测试都需要更新他们的期望,但这不是改变ComponentA(除非这些测试 ComponentA- 我通常看到它们在一个ComponentAIntegrationTests包中外部化,但我想它们也可以放在一起. 但这似乎不是本书所暗示的 - 它似乎是在谈论对代码的更改,而不是对测试的更改)
  • 如果 B 中的更改是对 以外的方法的签名更改doStuff,则 A 不需要任何更改
  • 如果 B 中的更改是对 的签名更改doStuff,则 A需要更改 - 但如果接口也在 A 中,情况就是如此。

(请注意,在清洁架构提倡的设置下,其中提供类的接口在消费组件(A)中,只有第一种情况会构成 B 的变化 - 所以这是我们真正需要关注的唯一一个和)

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

(请注意,我并不是反对使用接口——它们还有许多其他好处,尤其是允许在合同的“双方”同时开发,并允许交换接口的各种实现)

4

2 回答 2

2

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

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

首先要注意的是所有依赖都是源代码依赖。从 A 类指向 B 类的箭头表示 A 类的源代码中提到了 B 类的名称,但 B 类没有提及 A 类。 --第 72 页

那么问题就变成了,我们想要保护组件(罐子)免受哪些变化?答案是:重新编译、重新部署和传递依赖。

如果 componentA依赖于 component B,那么任何更改B(即使该更改不影响使用的 API A)都需要A重新编译。此外,如果更改B添加、删除或修改 的依赖项B,则A必须协调新的传递依赖项,因此更改会传播。

这种依赖意味着对 [ B] 源代码的更改将强制 [ A] 重新编译和重新部署,即使它关心的实际上并没有发生任何变化。——第 84 页

传递依赖违反了软件实体不应该依赖于它们不直接使用的东西的一般原则。——第 75 页

我在这里混合了不止一个 SOLID 原则的元素;但在处理变更时,它们具有潜在的共性。

于 2019-01-18T22:27:55.980 回答
0

关于我的背景,我确实拥有计算机科学学位和 20 多年的专业经验。

先说第一件事。该领域没有什么是真正明确定义的(好吧,一些与数学相关的东西是)。甚至是面向对象本身或封装、单一职责等基本事物,以及领域驱动设计、REST 等更复杂的事物。对所有事情都有截然不同的看法。有时流行的解释或这些东西是最糟糕的选择。

我想说的是,即使面对压倒性的权威,也要保持怀疑。质疑事物并总是试图找到支持某事的正当理由。不仅仅是“这种方式更易于维护”,这也只是一个假设,而不是证据。

我碰巧认为鲍勃叔叔是一个不太理想的面向对象的信息来源。如果您有兴趣,这是我前段时间写的对“清洁架构”(不是书,想法)的详细评论。

回到你的问题。引用本身基本上是说依赖的方向与变化的传播背道而驰(这里又是我的一篇文章,里面有一些漂亮的图片)。它并不意味着(也不应该)变化总是通过依赖关系回流,但它是“知识”。总是。有时更改不需要外观,例如签名更改。容量、性能的变化有时是破坏性的变化,但在签名中并不直接可见。方法的含义也可能在没有任何明显迹象的情况下发生变化。

所以,你是对的,内部变化可能根本不会引起任何其他变化。但是,避免某事物发生潜在变化的唯一方法是首先不依赖该事物。

第二个最好的方法是有一个很好的抽象和封装,不幸的是“清洁架构”做得不好。使用 Anemic Objects 和纯数据作为 Uncle Bob 提倡的接口会破坏组件可能具有的任何抽象或封装。

于 2018-12-26T23:01:29.717 回答