我只能描述我们想出的东西。我们从各种在线库中借用了可用性语法等,但代码都是我们的。
基本上,我们有我们所说的 ServiceContainer,一个对象。它总是有一个全局实例,如果你愿意,可以是一个单例副本,静态的,因此在 web 应用程序中,在 appdomain 中的所有用户之间共享。
ServiceContainer 包含规则。规则是这样说的:如果有人要一个 XYZ 类型的对象,这就是你如何为他们提供它的方法。
例如,一个规则可能是为了让某些代码获得一个实现 IDbConnection 的对象,容器将构造、配置并返回一个新的 SqlConnection 对象。
因此,有问题的代码不知道也不关心它使用的是 SqlConnection 对象,而不是 OleDbConnection 对象。
写完之后,我意识到这不是一个很好的例子,因为最终您会向连接请求命令对象,并且您为该对象提供的 SQL 语法必须根据连接类型进行调整。但是如果我们现在可以忽略这一点,代码就不会知道它正在连接到 SQL Server,它只知道它有一个连接对象。
现在,这里有问题的代码需要给出它应该使用的容器,因此需要给出规则。这意味着从单元测试的角度来看,我可以创建一个新的 ServiceContainer 实例,将新规则写入其中以进行测试,并要求代码执行它的操作。最终,代码会想要执行一些 SQl(在这种情况下),而不是与真正的数据库对话,它会调用我的 IDbConnection 和 IDbCommand 的测试实现,从而让我有机会验证事情是否正常。
更重要的是,它为我提供了一种返回适合测试的已知虚拟数据的方法,而无需模拟整个数据库。
现在,对于注入部分,在我们的例子中,我们可以要求容器为我们提供必须构建的对象,这些对象依赖于其他对象。
例如,假设我们有一个 IDataAccessLayer 接口和一个 MSSQLDataAccessLayer 实现。
虽然接口没有给我们任何外部迹象表明它进行了任何日志记录,但这里的实际实现需要有一个地方来记录它执行的所有 SQL。因此,该类的构造函数可能如下所示:
public MSSQLDataAccessLayer(ILogger logger) { ... }
在 ServiceContainer 对象中,我们注册了以下规则(这是我们的语法,在其他任何地方都找不到,但应该很容易遵循):
ServiceContainer.Global.RegisterFactory<ILogger, FileLogger>()
.FactoryScoped()
.WithParameters(
new Parameter("directory", @"C:\Temp")
);
ServiceContainer.Global.RegisterFactory<IDataAccessLayer, MSSQLDataAccessLayer>()
.FactoryScoped();
FactoryScoped 意味着每次我向容器请求一个对象时,我都会得到一个新对象。
规则,如果我用英文写,是这样的:
- 如果有人需要 ILogger 的实现,请构造一个新的 FileLogger 对象,并获取需要“目录”参数的构造函数,并在传入“C:\Temp”作为参数时使用该构造函数
- 如果有人需要 IDataAccessLayer 的实现,请构造一个新的 MSSQLDataAccessLayer
请注意,我之前说过 MSSQLDataAccessLayer 的构造函数采用 ILogger,但我没有在这里指定任何参数?这给了我以下代码来获取访问层对象:
IDataAccessLayer dal = ServiceContainer.Global.Resolve<IDataAccessLayer>();
现在发生的情况是容器对象发现该对象是 MSSQLDataAccessLayer,并且它有一个构造函数。这个构造函数需要一个 ILogger 对象,但是你瞧,容器知道如何创建一个。容器将因此构造一个新的 FileLogger 对象,并将其传递给 MSSQLDataAccessLayer 对象的构造函数,静默。
因此,大部分应用程序依赖项的配置可以一次完成,在某个中央位置并在启动期间执行,而其余代码则完全没有意识到这里发生的所有魔法。
出于单元测试的目的,我可以重写规则以提供我自己的虚拟记录器对象,该对象仅将记录的文本存储在内存中,这使我能够轻松验证我期望记录的代码是否实际记录,而无需读取之后归档。
这些规则为我们提供了如何实际提供对象实例的强大功能:
- 来自委托/方法,这意味着如果我们愿意,我们可以自己完成构建依赖对象的所有魔法
- 从构造函数中自动生成(或者自动确定要使用哪一个,或者我们可以通过名称/类型提供足够的虚拟参数来选择一个)
- 或者我们可以为容器提供一个现有实例(这将有点像单例)
在提出我们自己的方法之前,我们查看了autofac,基本上我们只是查看了显示调用语法示例的 wiki,然后坐下来编写了我们自己的系统来满足我们的需要。