听起来你现在的位置是这样的:
a) 您不希望类创建它们所依赖的其他类,因为这会将它们耦合在一起。每个类都必须对它所依赖的类了解太多,例如它们的构造函数参数。
b) 你创建一个工厂来分离这些对象的创建。
c) 你发现你在 (a) 中遇到的问题现在已经转移到 (b) 中,但它是完全相同的问题,只是有更多的类。现在您的工厂必须创建类实例。但是它将从哪里获得创建这些对象所需的构造函数参数?
一种解决方案是使用 DI 容器。如果这完全熟悉,那么这就是 10% 的坏消息和 90% 的好消息。有一点学习曲线,但还不错。 90% 的好消息是,您已经意识到自己需要它,并且它将成为一个非常有价值的工具。
当我说“DI 容器”时 - 也称为“IoC(控制反转)容器”,它指的是 Autofac、Unity 或 Castle Windsor 等工具。我主要与温莎一起工作,所以我在示例中使用它。
DI 容器是一种无需显式调用构造函数即可为您创建对象的工具。 (这个解释 100% 肯定是不够的 - 你需要更多地谷歌。相信我,这是值得的。)
假设您有一个依赖于多个抽象(接口)的类。这些接口的实现依赖于更多抽象:
public class ClassThatDependsOnThreeThings
{
private readonly IThingOne _thingOne;
private readonly IThingTwo _thingTwo;
private readonly IThingThree _thingThree;
public ClassThatDependsOnThreeThings(IThingOne thingOne, IThingTwo thingTwo, IThingThree thingThree)
{
_thingOne = thingOne;
_thingTwo = thingTwo;
_thingThree = thingThree;
}
}
public class ThingOne : IThingOne
{
private readonly IThingFour _thingFour;
private readonly IThingFive _thingFive;
public ThingOne(IThingFour thingFour, IThingFive thingFive)
{
_thingFour = thingFour;
_thingFive = thingFive;
}
}
public class ThingTwo : IThingTwo
{
private readonly IThingThree _thingThree;
private readonly IThingSix _thingSix;
public ThingTwo(IThingThree thingThree, IThingSix thingSix)
{
_thingThree = thingThree;
_thingSix = thingSix;
}
}
public class ThingThree : IThingThree
{
private readonly string _connectionString;
public ThingThree(string connectionString)
{
_connectionString = connectionString;
}
}
这很好,因为每个单独的类都简单且易于测试。但是你到底要如何创建一个工厂来为你创建所有这些对象呢?该工厂必须知道/包含创建每个对象所需的一切。
单独的类更好,但是组合它们或创建实例成为一个主要的问题。如果您的代码的某些部分只需要其中一个怎么办 - 您是否创建另一个工厂?如果您必须更改其中一个类以使其具有更多或不同的依赖项怎么办?现在你必须回去修理你所有的工厂。这是一场噩梦。
DI 容器(同样,此示例使用Castle.Windsor)允许您执行此操作。起初它看起来像是更多的工作,或者只是解决问题。但事实并非如此:
var container = new WindsorContainer();
container.Register(
Component.For<ClassThatDependsOnThreeThings>(),
Component.For<IThingOne, ThingOne>(),
Component.For<IThingTwo, ThingTwo>(),
Component.For<IThingThree, ThingThree>()
.DependsOn(Dependency.OnValue("connectionString", ConfigurationManager.ConnectionStrings["xyz"].ConnectionString)),
Component.For<IThingFour,IThingFour>(),
Component.For<IThingFive, IThingFive>(),
Component.For<IThingSix, IThingSix>()
);
现在,如果你这样做:
var thing = container.Resolve<ClassThatDependsOnThreeThings>();
或
var thingTwo = container.Resolve<IThingTwo>();
只要你已经向容器注册了类型,并且你还注册了满足所有嵌套依赖项所需的任何类型,容器就会根据需要创建每个对象,调用每个对象的构造函数,直到它可以最后创建您要求的对象。
您可能会注意到的另一个细节是,这些类都没有创建它们所依赖的东西。没有new ThingThree()。每个类所依赖的任何东西都在其构造函数中指定。这是依赖注入的基本概念之一。如果一个类只是接收和IThingThree 的实例,那么它真的永远不知道实现是什么。它只依赖于接口,对实现一无所知。这适用于依赖倒置,即 SOLID 中的“D”。它有助于防止您的类与特定的实现细节耦合。
这是非常强大的。这意味着,当正确配置时,您可以在代码中的任何位置请求您需要的依赖项——通常作为接口——然后接收它。需要它的类不必知道如何创建它。这意味着 90% 的时间你甚至根本不需要工厂。你的类的构造函数只是说明它需要什么,容器提供它。
(如果您确实需要一个工厂,在某些情况下确实会发生这种情况,Windsor 和其他一些容器会帮助您创建一个。Here's an example。)
要使其工作的一部分涉及学习如何配置您正在使用的应用程序类型以使用 DI 容器。例如,在 ASP.NET MVC 应用程序中,您可以配置容器来为您创建控制器。这样,如果您的控制器依赖于更多的东西,容器可以根据需要创建这些东西。 ASP.NET Core 提供了自己的 DI 容器,让您更轻松地注册各种组件。
这是一个不完整的答案,因为它描述了解决方案是什么,而没有告诉您如何实现它。这将需要您进行更多搜索,例如“我如何配置 XYZ 以进行依赖注入”,或者只是总体上了解更多有关该概念的信息。一位作者称其为 0.50 美元概念的 5 美元术语。在您尝试并了解其工作原理之前,它看起来很复杂且令人困惑。然后你就会明白为什么它会内置在 ASP.NET Core、Angular 中,以及为什么各种语言都使用依赖注入。
当您达到 DI 解决问题的地步时,这真的很令人兴奋,因为这意味着您意识到必须有一种更好、更清洁的方法来完成您正在尝试做的事情。好消息是有。学习和使用它将在整个代码中产生连锁反应,使您能够更好地应用 SOLID 原则并编写更易于单元测试的较小类。