DI:组合物根分解

时间:2017-08-13 12:05:17

标签: dependency-injection dependencies inversion-of-control code-injection

Composition root看起来非常奇怪。我们有一个非常大的上帝对象,它知道任何事情。

将组合根分解为某些模块的正确方法是什么,这些模块将封装对象图的自身部分的初始化?

hierarchical dependency injection怎么办?

3 个答案:

答案 0 :(得分:8)

我不同意组合根是God Object的前提。虽然从概念上讲,组合根可能包含许多代码行,但它只有single responsibility:组成一个对象图。

组合根可以包含很多数据,并且它本身可以耦合到整个应用程序,但它在概念上只有一个方法。它只有一个改变的理由,那就是你想要改变应用程序的组合方式。

此外,当Composition Root与应用程序的其余部分耦合时,应用程序代码不知道组合根。因此,根据Dependency Inversion Principle,耦合仅在一个方向上进行。

一种表达方式是God对象违反了所有五个SOLID principles,而一个组合根至少跟随SRP,ISP和DIP。我不认为OCPLSP适用于此处。

所有这一切,如果一个组合根组成一个大的应用程序,它仍然可以包含很多代码。如果您发现它变得无法管理,那么您应该如何将其分解为更小的代码块?

与分解任何其他代码的方式相同。它取决于。

我希望我从未写过或说组合根只能是一个单一的类。如果我这样做,我想收回那个。在撰写作文集时,我经常将其职责分为多个班级(尽管很少超过一些)。您可以应用任何您想要的设计模式或其他OOD技术来实现该目标。然后,Composition Root将成为您实现的Facade

我无法提供更多指导,因为它取决于应用程序架构。如果进行垂直切片,则很可能需要以不同于水平分层的方式构造组合根,等等。

那就是说,作为一般规则,我认为尝试将依赖图分解为子树是有用的。首先,因为依赖图是图形,而不是树,但也因为您经常需要交错多个独立的问题。例如,您可以拥有负责应用程序各个子部分的子图,但是您需要在整个过程中交错相同的日志记录机制,相同的缓存机制或相同的安全机制等。整个图表。如果你试图将它们分开,你就不能轻易做到这一点。

答案 1 :(得分:2)

我认为社区对于组合根的外观并不完全一致。

例如,您可以通过练习Pure Dependency Injection (Pure DI)手动连接对象,或者您可以使用依赖注入容器并手动注册映射,或者您可以使用依赖注入容器并围绕类创建一些约定接口名称实现所谓的约定优于配置。或者也许你可以在这三种方法之间进行混合。

另一个例子:你可以选择连接Composition Root中的所有内容(在我看来是更好的选项),或者你可以选择为每个组件(例如.NET中的类库)设置一个“小的组合根”系统,然后将这些根连接到“主要组合根”。

根据您选择的方法,组合根的知识会有所不同。

例如,如果您使用Pure DI并且只有一个Composition Root,那么Composition Root将需要非常了解该应用程序。

另一方面,如果你遵循约定优于配置方法,那么关于哪些类应该映射到哪些接口的一些知识将分布在各个地方(在类和接口名称内等)。 / p>

根据我的经验,具有单一组合根的Pure DI +是任何足够大的应用程序的最佳选择。这为您提供了理解应用程序的中心位置,并使您成为Composition Root navigable

这意味着Composition Root有很大的责任。我不确定我们是否可以将其称为上帝对象。我认为这个术语创造的背景是不同的。

我不会说组合根确实“太”了。虽然它确实很多,但我认为应该这样做。如果您和我一样,那么您需要一个位置,您可以在其中导航和理解您的应用程序。

要分解您的Composition Root,您可以将其拆分为多个方法。我有时在C#(多个文件中的单个类)中创建一个部分类,并将相关方法放入同一个文件中。

虽然应用程序图是一个图形,但如果它是一棵树,它将更容易分解。我尽力使图形树状。如果两个对象需要类似的依赖关系,我会尝试给它们不同的依赖实例,以维护图形的树状结构。

我写了一篇关于这个主题的文章,你可以阅读here

Here也是另一篇文章,提供了对组合根的责任的一些观点。

答案 2 :(得分:1)

组合根不是真正的上帝对象,因为它只知道对象图中未封装的部分。

在组件层次结构中,组合根有一定数量的子项,它创建的对象图表实现了这些子项之间的相互依赖关系。交易门户可能配置有股票策略,共同基金策略和经纪人界面。策略需要代理接口,门户UI需要一个或多个策略。组合根将它们连接在一起。

现在,任何这些注入的依赖项 - 交易策略或代理接口都可能有很多内部功能,也可以通过依赖注入来配置,但组合根不应该有任何关于那些内部的硬编码知识。相反,组合根可以使用 factory 实现进行配置,以构建复杂的依赖关系。

工厂实现可能需要组合根目录中的资源。例如,交易策略工厂都需要代理接口实现。但是它们都需要组合根中的相同的事物,因此组合根不需要知道它们的具体细节。

如有必要,传递给工厂的资源可以包括对组合根不透明的工厂的DI配置。因此,工厂可以创建适合实现他们应该创建的对象的任何对象图。

重要的是要注意工厂接口 - 应用程序调用它构建所需对象的东西 - 特定于应用程序。在满足特定类型依赖关系的所有可配置方式中,相同。例如,上面将有一个TradingStrategyFactory界面,共同基金(使用共同基金模块)和股票(使用股票模块)的不同实现。

除非工厂界面以某种方式标准化,否则工厂界面和实现是应用程序的一部分,而不是实现其使用功能的模块的一部分。

有时,需求会发生变化,某些新模块将需要组合根未提供的资源或连接。这将需要更改工厂界面及其所有实现。 NOT 应该要求对其他模块进行更改,因此这些工厂实现正是应用程序本身的一部分。

所以,最后......你的问题的答案是,一个庞大而复杂的组合根分解为各种工厂实现。

您还在Angular中提到了分层DI。这是一种将配置的资源自动传递到组件工厂的机制。我不熟悉Angular,但我猜它是必需的,因为Angular试图为所有组件标准化单个工厂接口。这具有以难以理解的方式将对象图本身的构造移动到DI配置中的效果。如果您使用.net进行编程,则不需要这样做,我会在该环境中避免使用它。