参考地址:
https://docs.microsoft.com/en-us/previous-versions/msp-n-p/dn170416(v=pandp.10)
总览
Unity是一个轻量级的,可扩展的依赖项注入容器,支持构造函数,属性和方法调用注入。
- 简化的对象创建,尤其是对于分层对象结构和依赖关系。
- 需求抽象;这使开发人员可以在运行时或配置中指定依赖关系,并简化横切关注点的管理。
- 通过将组件配置推迟到容器来提高灵活性。
- 服务定位功能,允许客户端存储或缓存容器。
- 实例和类型拦截(Unity for Windows Store应用程序不支持)。
Unity是一个通用容器,可用于任何类型的基于Microsoft.NET Framework的应用程序。它提供了依赖注入机制中常见的所有功能,包括注册类型映射和对象实例,解析对象,管理对象生存期以及将依赖对象注入到构造函数和方法的参数中以及作为对象属性值的方法。
Unity是可扩展的。您可以编写更改容器行为或添加新功能的容器扩展。例如,Unity提供的拦截功能可作为容器扩展来实现,您可以使用该功能来捕获对对象的调用并向目标对象添加其他功能和策略。
目标:松散耦合系统。
为什么要使用
优势
可维护性
可测试性:测试驱动开发(TDD)等方法要求您在编写任何代码以实现新功能之前编写单元测试,而这种设计技术的目标是提高应用程序的质量。
灵活性和可扩展性
后期绑定
并行开发
横切关注点(Crosscutting Concerns):验证,异常处理和日志处理
松耦合
例子
未使用Unit:Tenant租赁
TenantStore:一个存储库,该存储库处理对基础数据存储(如关系数据库)的访问。
ManagementController:一个MVC控制器类,它从存储库中请求数据。
ManagementController类必须实例化TenantStore对象或从其他位置获取对TenantStore对象的引用,然后才能调用GetTenant和GetTenantNames方法。该ManagementController类依赖于特定的,具体TenantStore类。
public class TenantStore
{
...
public Tenant GetTenant(string tenant)
{
...
}
public IEnumerable<string> GetTenantNames()
{
...
}
}
public class ManagementController
{
private readonly TenantStore tenantStore;
public ManagementController()
{
tenantStore = new TenantStore(...);
}
public ActionResult Index()
{
var model = new TenantPageViewData<IEnumerable<string>>
(this.tenantStore.GetTenantNames())
{
Title = "Subscribers"
};
return this.View(model);
}
public ActionResult Detail(string tenant)
{
var contentModel = this.tenantStore.GetTenant(tenant);
var model = new TenantPageViewData<Tenant>(contentModel)
{
Title = string.Format("{0} details", contentModel.Name)
};
return this.View(model);
}
...
}
可能出现的问题:
- 如果TenantStore变化,实现多态,这边的依赖已经被写死,不能更改
- 在单元测试中ManagementController需要实例化TenantStore对象,测试变得复杂
- 不能通过后期绑定的方式,在编译的过程中直接被编译成了TenantStore类。
针对接口编程,对类修改封闭,扩展开放,
改造:
public interface ITenantStore
{
void Initialize();
Tenant GetTenant(string tenant);
IEnumerable<string> GetTenantNames();
void SaveTenant(Tenant tenant);
void UploadLogo(string tenant, byte[] logo);
}
public class TenantStore : ITenantStore
{
...
public TenantStore()
{
...
}
...
}
public class ManagementController : Controller
{
private readonly ITenantStore tenantStore;
public ManagementController(ITenantStore tenantStore)
{
this.tenantStore = tenantStore;
}
public ActionResult Index()
{
...
}
public ActionResult Detail(string tenant)
{
...
}
...
}
好处:
- 易于维护,当TenantStore类发生变化时,不需要修改控制器,
- 实现后期绑定。
- 两个团队可以在控制器和TenantStore上并行工作。
怎么用
什么时候使用?
什么位置使用?
常见依赖模式
工厂方法模式
public class ManagementController : Controller
{
protected ITenantStore tenantStore;
public ManagementController()
{
this.tenantStore = CreateTenantStore();
}
protected virtual ITenantStore CreateTenantStore()
{
var storageAccount = AppConfiguration
.GetStorageAccount("DataConnectionString");
var tenantBlobContainer = new EntitiesContainer<Tenant>
(storageAccount, "Tenants");
var logosBlobContainer = new FilesContainer
(storageAccount, "Logos",
"image/jpeg");
return new TenantStore(tenantBlobContainer,
logosBlobContainer);
}
public ActionResult Index()
{
var model = new TenantPageViewData<IEnumerable<string>>
(this.tenantStore.GetTenantNames())
{
Title = "Subscribers"
};
return this.View(model);
}
...
}
public class SQLManagementController : ManagementController
{
protected override ITenantStore CreateTenantStore()
{
var storageAccount = ApplicationConfiguration
.GetStorageAccount("DataConnectionString");
var tenantSQLTable = ...
var logosSQLTable = ....
return new SQLTenantStore(tenantSQLTable, logosSQLTable);
}
...
}
该方法实现了对修改封闭对扩展开放,但还是很难测试ManangementController类,
简单工厂模式
public class ManagementController : Controller
{
private readonly ITenantStore tenantStore;
public ManagementController()
{
var tenantStoreFactory = new TenantStoreFactory();
this.tenantStore = tenantStoreFactory.CreateTenantStore();
}
public ActionResult Index()
{
var model = new TenantPageViewData<IEnumerable<string>>
(this.tenantStore.GetTenantNames())
{
Title = "Subscribers"
};
return this.View(model);
}
...
}
由一个全新的工厂TenantStore来负责创建,该模式消除了ManangementController对特定TenantStore的依赖。
大型应用程序中使用简单工厂可能引起问题是难以保持一致性,所以工厂要进行多态,可能需要各种工厂BlobStoreFactory和SqlStoreFactory,就是抽象工厂模式
服务定位器模式
服务定位模式可以视为一个注册表,应用程序中的一个类在该服务定位器中创建并注册对象或服务的实例。与工厂模式不同的是,服务定位器负责管理对象的生命周期,并将简单的应用返回给客户端。
具体可以查看https://martinfowler.com/articles/injection.html的使用服务定位器一节。
常见依赖的优缺点
常见依赖注入的共同特征是:高级客户端对象仍然对请求特定实例的对象存在依赖。而且其都采用不同复杂程度的拉模型将职责分配给工厂。拉模型还使得客户端对工厂也存在依赖,并隐藏在类的内部。
依赖注入则是通过推模型替代拉模型。
依赖注入时,另一个类负责在运行时将依赖项注入到客户端中,也就是ManagementController类,
public class ManagementController : Controller
{
private readonly ITenantStore tenantStore;
public ManagementController(ITenantStore tenantStore)
{
this.tenantStore = tenantStore;
}
public ActionResult Index()
{
var model = new TenantPageViewData<IEnumerable<string>>
(this.tenantStore.GetTenantNames())
{
Title = "Subscribers"
};
return this.View(model);
}
...
}
这样做的好处是客户端不了解负责实例化ITenantStore对象的类或组件。
依赖注入管理对象
怎么创建
DependencyInjectionContainer类可以管理多个类似工厂类的依赖,并提供一些附加功能,如生命周期的管理,拦截和按约定的注册。
如果使用依赖注入,需要将一些类或组件的依赖项伴随着构造函数传递进去,同时也意味着由一些类或组件必须负责这些依赖项的实例化并将它们传入到正确的构造函数、方法和属性。这些类和组件通常是:在控制台应用程序中是Main方法,Web应用程序中是Global.asax,Windows Azure的Role's Onstart方法。
生命周期
哪个对象负责管理状态,对象是否共享以及对象将生存多长时间。创建对象总是要花费有限的时间,该时间取决于对象的大小和复杂性,一旦创建了对象,它就会占用系统的某些内存。
可能希望每个客户端类都具有自己的ITenantStore对象,或者希望所有客户端类共享相同的ITenantStore实例,或者对于不同组的客户端类,每个客户端类都具有自己的ITenantStore实例。
构造函数注入
属性注入
public class AzureTable<T> : ...
{
public AzureTable(StorageAccount account)
: this(account, typeof(T).Name)
{
}
...
public IAzureTableRWStrategy ReadWriteStrategy
{ get; set; }
...
}
使用属性注入时相比于构造函数注入有可能以往,但在依赖项可选的时候,应该使用属性注入器。
方法注入
public class MessageQueue<T> : ...
{
...
public MessageQueue(StorageAccount account)
: this(account, typeof(T).Name.ToLowerInvariant())
{
}
public MessageQueue(StorageAccount account,
string queueName)
{
...
}
public void Initialize(TimeSpan visibilityTimeout,
IRetryPolicyFactory retryPolicyFactory)
{
...
}
...
}
方法注入:在此示例中提供了Initialize方法中注入,当提供一些正在使用的对象上下文时不能使用构造函数注入,可以使用方法注入的方法。
不应使用依赖注入
- 依赖注入在小型应用程序中显得过大,从而导致额外的复杂性和不适当无用要求
- 在大型应用程序中,依赖注入会使得代码和正在发生的事情变得难以理解
- 类型注册和解析会导致性能损失:解析几乎可以忽略不计,注册只进行一次。
- 依赖注入在功能上的重要性要小得多,当可测试性,故障恢复和并行性是关键要求时,函数式编程正变得越来越普遍。