Autofac 模块
简介
IoC 使用 组件 作为应用程序的基本构建块。通过提供对组件的构造参数和属性的访问,常常用作实现 部署时配置 的方式。
这通常是一个可疑的做法,原因如下:
- 构造函数可能会更改:组件的构造函数签名或属性的更改可能会破坏已部署的
App.config
文件——这些问题可能在开发过程的后期才会出现。 - JSON/XML 难以维护:大量组件的配置文件可能变得难以维护。
- 代码开始出现在配置中:暴露类的属性和构造参数是对应用程序内部“封装”的不愉快侵犯——这些细节不属于配置文件。
这就是模块发挥作用的地方。
**模块是一个小类,可以用来将一组相关组件包装在一个“外观”后面,以简化配置和部署。**模块故意限制了配置参数的数量,这些配置参数可以根据实现模块的组件独立地更改。
模块内的组件仍然在组件/服务级别使用依赖关系来从其他模块访问组件。
**模块本身不会经历依赖注入。**它们用于配置容器,而不是像其他组件那样注册和解决。例如,如果你的模块接受一个构造参数,你需要自己传递它。它不会来自容器。
模块的优势
减少配置复杂性
通过 IoC 配置应用程序时,常常需要在多个组件之间设置参数。模块将相关配置项组合到一个地方,减少了查找正确设置所对应组件的负担。
模块的实现者确定如何将模块的配置参数映射到内部组件的属性和构造参数。
明确的配置参数
直接通过组件配置应用程序会产生一个庞大的表面区域,升级应用程序时需要考虑。如果可以通过配置文件设置任何类的任何属性,并且每个站点都不同,那么重构就不再安全。
创建模块限制了用户可以配置的配置参数,并使维护程序员明确知道这些参数是什么。
您还可以避免在编写良好程序元素与编写良好配置参数之间的权衡。
隔离内部应用程序架构
通过组件配置应用程序意味着配置需要根据诸如使用枚举与创建策略类等事情而有所不同。使用模块隐藏了应用程序结构的这些细节,保持配置简洁。
更好的类型安全性
当应用程序由可变的类组成时,总是存在一定程度的类型安全性损失。然而,通过 XML 配置大量组件会加剧这个问题。
模块是通过程序化方式构建的,所以其中的所有组件注册逻辑都可以在编译时检查。
动态配置
模块内的组件配置是动态的:模块的行为可以根据运行环境变化。如果只通过组件配置,这几乎是不可能的。
高级扩展
模块可用于不仅仅是简单的类型注册——您还可以附加到组件解析事件,并扩展参数如何解析或其他扩展。log4net 集成模块示例 展示了这样一个模块。
示例
在 Autofac 中,模块实现了 Autofac.Core.IModule
接口。通常,它们会从 Autofac.Module
抽象类派生。
这个模块提供了 IVehicle
服务:
public class CarTransportModule : Module
{
public bool ObeySpeedLimit { get; set; }
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<Car>(c => c.Resolve<IDriver>()).As<IVehicle>();
if (ObeySpeedLimit)
builder.RegisterType<SaneDriver>().As<IDriver>();
else
builder.RegisterType<CrazyDriver>().As<IDriver>();
}
}
封装配置
我们的 CarTransportModule
提供了 ObeySpeedLimit
配置参数,而不暴露这是通过选择理智或疯狂驾驶员实现的事实。使用模块的客户端可以通过声明其意图来使用它:
builder.RegisterModule(new CarTransportModule() {
ObeySpeedLimit = true
});
或者在 Microsoft.Extensions.Configuration
的 configuration 格式 中:
{
"modules": [
{
"type": "MyNamespace.CarTransportModule, MyAssembly",
"properties": {
"ObeySpeedLimit": true
}
}
]
}
这很有价值,因为模块的实现可以根据需要而变化,而不会产生连锁效应。毕竟,这就是封装的概念。
覆盖灵活性
尽管 CarTransportModule
的客户端可能主要关心 IVehicle
服务,但模块也向容器注册其 IDriver
依赖项。这确保了配置仍然可以在部署时以与独立注册组件相同的方式被覆盖。
使用 Autofac 时,推荐的做法是在程序化配置之后添加任何 XML 配置,例如:
builder.RegisterModule(new CarTransportModule());
builder.RegisterModule(new ConfigurationSettingsReader());
这样,“紧急” 覆盖可以在 配置文件 中进行:
{
"components": [
{
"type": "MyNamespace.LearnerDriver, MyAssembly",
"services": [
{
"type": "MyNamespace.IDriver, MyAssembly"
}
]
}
]
}
因此,模块增加了封装性,但并不妨碍您在必要时调整其内部。
适应部署环境
模块可以是动态的——也就是说,它们可以根据执行环境自定义配置。
加载模块时,它可以做一些有趣的事情,比如检查环境:
protected override void Load(ContainerBuilder builder)
{
if (Environment.OSVersion.Platform == PlatformID.Unix)
RegisterUnixPathFormatter(builder);
else
RegisterWindowsPathFormatter(builder);
}
模块的常见用例
- 配置提供子系统的相关服务,例如使用
NHibernate
的数据访问 - 包装可选的应用程序功能作为 “插件”
- 提供预构建的包以与系统集成,例如会计系统
- 注册经常一起使用的相似服务,例如一组文件格式转换器
- 新的或定制的容器配置机制,例如使用模块实现
JSON/XML
配置;可以通过这种方式添加使用属性的配置