面向对象编程 - 嵌套对象依赖的长链是反模式吗?

4
我试图编写使用依赖注入的代码,既可以进行模拟,也可以具有更清晰、更明确的设计。
我经常遇到一个问题,我认为这是很常见的,但我还没有找到任何能帮助我克服它的东西。问题是A对象依赖于B,B依赖于C,C依赖于D,以此类推,在这个链中可能会有很多链接。
看起来,为了实践依赖注入,A需要在其构造函数中请求B、C、D等(或者对于创建它们所依赖的实例的对象,如BFactory、CFactory等)。为了论证,我假设这些依赖关系不是可选的或仅限于特定方法,因此不能使用setter注入或方法参数注入。
这让我想到长链式依赖对象是一种反模式。从抽象意义上讲,它与箭头反模式有一些共同之处。长链式依赖对象形成了一个箭头形状的序列图。
因此,也许我应该避免这种做法,并遵循“Python之禅”中的建议,“平坦比嵌套好”。这建议采用一种设计,其中主程序创建两个或三个对象,它们合作产生返回给主程序的结果,然后主程序创建另外两个或三个对象来完成下一阶段的工作,以此类推。
我感觉这种代码易于理解和调试,并使依赖注入变得容易。但它似乎违反了“告诉,不要问”的原则,并使主程序太臃肿。我喜欢主程序如此小而明显,以至于它不需要单元测试。如果所有东西都以A开头并以A结尾,那么它就是A的责任,“告诉,不要问”告诉我,如果A是一个正在计费的客户,并且客户拥有启动计费过程所需的数据以及最后需要发送发票的电子邮件地址,那么A应该自己完成工作(主程序只能调用Customer.billYourself()),而不是通过给主程序一堆发票和一个电子邮件地址来将责任交还给主程序。
所以,我应该避免依赖链,以使DI更容易,还是应该因为“告诉,不要问”而接受它们呢?

1
这是一个非常有趣但非常难以回答的问题,如果没有具体的例子来谈论,可能会更多地涉及观点而非事实,因为设计哲学差异很大。 - Joachim Isaksson
你的评论让我意识到,我正在处理的所有示例都是遗留代码,我正在重构以实现可测试性。如果我发布它们,普遍存在的糟糕设计会带来更多干扰而不是澄清。我会尝试想出一个问题。 - naomi
1
好的,我试图想出一个情况,在一个新应用程序中我会被诱惑使用这样的长链,但我想不出来。这可能是因为它们是反模式,只会在其他问题存在的应用程序中出现。 - naomi
1个回答

4

类有许多依赖关系,这是事实。但是,这些类之间的依赖关系决定了整个系统的优劣。

您应该了解稳定依赖原则。如果遵循此规则,则依赖关系的数量不应成为问题:

设计中包之间的依赖关系应沿着包的稳定性方向。一个包只应该依赖于比它更稳定的包。

有好的依赖关系,也有坏的依赖关系:

因此,我们可以说“良好的依赖关系”是对低波动性事物的依赖。目标依赖关系越不稳定,依赖关系就越“好”。同理,“不良的依赖关系”是指对不稳定事物的依赖。目标依赖关系越不稳定,依赖关系就越“不好”。
将应用程序结构化以具有良好的依赖关系。

2
我理解这个原则,但我不明白它与我的问题有什么关系。问题在于必须将 C 和 D 传递到 A 的构造函数中,而 A 并不直接与它们合作,而是将它们传递给 B,B 再将 D 传递给 C 供 C 使用。无论稳定性的方向如何,A 是否需要了解 D 都会破坏封装性吗?如果 C 不再需要 D,A 就必须进行更改。无论 C 是否需要 D,这都是实现细节,A 不应该关心。 - naomi
A 是否与 C 有交互?如果没有,那么是的,这会破坏封装性,并且让一个类依赖于仅用于传递给另一个类的东西是不好的。而不是构造函数注入,你可以使用 DI 容器并让每个类从容器中获取它所需的内容。 - Bob Horn
谢谢 - 那很有道理。(现在我想起来了,我的先前评论并不合适。) 不幸的是,我不能在这个项目中使用 DI 容器。但我认为可以用工厂来实现。如果 A 需要 C 来创建 B,那么 A 可以请求一个 BFactory,而 BFactory 可以首先制造一个 C。虽然不如容器那样灵活,但仍然封装和可模拟... - naomi
很棒,听起来你找到了一个好的解决方案。如果我的回答没有帮助到你,我可以删除它。祝你好运! - Bob Horn
1
嗯,不确定问题是否会以FactoryFactoryFactories的形式再次出现...无论如何,没有必要删除,C++工程系列可以使用一些提示。 - naomi
无论如何,我的问题基本上是这个的重复:https://dev59.com/PUzSa4cB1Zd3GeqPnIGx - naomi

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