实现 Microsoft Unity(依赖注入)设计模式






4.68/5 (13投票s)
介绍 Microsoft 的 Unity 框架,特别是构造函数注入模式。
引言
本文旨在解释构造函数注入原理,特别是单例方法(其方法非常静态,但在设计模式中有其作用——例如,对于不需要在每个使用它的类中重新实例化的日志记录器类)。
什么是依赖注入(回顾)
依赖注入也称为控制反转 (IOC)。它是一种设计模式,可以消除依赖组件之间紧密的耦合。它有助于在软件中设计和实现松散耦合、可重用且可测试的对象。
何时使用 Unity 依赖注入?
依赖注入提供了简化代码、抽象对象之间依赖关系以及自动生成依赖对象实例的机会。通常,当出现以下情况时,您应该使用 Unity:
- 您的对象和类可能依赖于其他对象或类。
- 您的依赖项需要抽象。
- 您想利用构造函数注入的特性。
- 您想管理对象实例的生命周期。
- 您想拦截对方法或属性的调用,以生成包含实现横切任务的处理器(handlers)的策略链或管道。
Unity 的优点
Unity 为开发人员提供了以下优势:
- 它提供了简化的对象创建,特别是对于分层对象结构和依赖项,这简化了应用程序代码。它包含一个用于构建(或组装)对象实例的机制,这些对象可能包含其他依赖对象实例。
- 它支持需求抽象;这允许开发人员在运行时或配置中指定依赖项,并简化横切关注点的管理。
- 它通过将组件配置推迟到容器来提高灵活性。它还支持容器的层次结构。
- 它具有服务定位功能,这在应用程序反复使用解耦和集中功能组件的许多场景中非常有用。
- 它允许客户端存储或缓存容器。这在 ASP.NET Web 应用程序中尤其有用,开发人员可以将容器持久化到 ASP.NET 会话或应用程序中。
- 它具有拦截功能,允许开发人员通过创建和使用在方法或属性调用到达目标组件之前执行的处理器,以及在调用返回时再次执行的处理器来为现有组件添加功能。
- 它可以从标准配置系统(如 XML 文件)读取配置信息,并使用它来配置容器。
- 它不对对象类定义提出要求。除了使用属性或方法调用注入外,不需要将属性应用于类,并且类声明没有限制。
- 它支持您可以实现的自定义容器扩展;例如,您可以实现方法以允许额外的对象构造和容器功能,例如缓存。
- 它使架构师和开发人员能够更轻松地实现现代应用程序中常见的通用设计模式。
Unity 可以创建的对象类型
您可以使用 Unity 容器生成任何具有 public
构造函数的对象的实例(换句话说,可以使用 new
运算符创建的对象),而无需向容器注册该类型的映射。当您调用 Resolve
方法并指定未注册类型的默认实例时,容器将简单地调用该类型的构造函数并返回结果。
管理对象生命周期
Unity 允许您选择其创建的对象生命周期。默认情况下,每次解析某个类型时,Unity 都会创建该类型的新实例。但是,您可以使用生命周期管理器来为已解析的实例指定不同的生命周期。例如,您可以指定 Unity 只维护一个实例(实际上是单例)。只有在没有现有实例时,它才会创建一个新实例。如果存在现有实例,它将返回对该实例的引用。此外,您可以使用一个生命周期管理器,它只持有对对象的弱引用,以便创建过程可以处理它们,或者使用一个生命周期管理器,它为每个单独解析它的线程维护一个单独的对象单例。
代码解释
将接口注册到 Unity
在下面的行中,我们创建了 UnityContainer
的实例。
myContainer = new UnityContainer();
我们创建的第一个注册类型(下面的代码片段)是“Employee
”类。该类将“Person
”类作为构造函数参数。我们为该注册指定了一个名称“Person-Non-Singleton
”,稍后我们可以使用它来引用这个特定的“Employee
”类。“Person
”类在此实例中每次都会被重新创建(不是单例生命周期)。
// Employee class with Person (non-singleton) constructor parameter
myContainer.RegisterType<IEmployee, Employee>("Person-Non-Singleton");
myContainer.Resolve<IEmployee>("Person-Non-Singleton");
下一个注册,我们将“Employee
”类注册为接收一个映射参数(在本例中映射名称为“Person-Singleton
”)的单例“Person
”类。映射名称用于我们的代码以引用特定的已注册类型,因为“Employee
”类可以通过多种方式生成。
// Employee class with Person (singleton) constructor parameter
myContainer.RegisterType<IEmployee, Employee>("EmployeeWithPersonSingleton",
new InjectionConstructor(new ResolvedParameter<Person>("Person-Singleton")));
myContainer.Resolve<IEmployee>("EmployeeWithPersonSingleton");
以下代码显示了将用于默认“Employee
”创建的“Person
”类的注册。在这种情况下,“Person
”类不是单例,也不会保留可能已分配给先前单例生命周期“Person
”类的任何值。
// Person (non-singleton)
myContainer.RegisterType<IPerson, Person>("Person-Non-Singleton");
myContainer.Resolve<IPerson>("Person-Non-Singleton");
最后,注册“Person
”类,但作为单例生命周期实例,因此当它再次被引用时,它将保留之前分配给它的任何先前值。
// Person (singleton)
myContainer.RegisterType<IPerson, Person>
("Person-Singleton", new ExternallyControlledLifetimeManager());
myContainer.Resolve<IPerson>("Person-Singleton");
try
{
myContainer = new UnityContainer();
// Employee class with Person (non-singleton) constructor parameter
myContainer.RegisterType<IEmployee, Employee>("Person-Non-Singleton");
myContainer.Resolve<IEmployee>("Person-Non-Singleton");
// Employee class with Person (singleton) constructor parameter
myContainer.RegisterType<IEmployee, Employee>
("EmployeeWithPersonSingleton", new InjectionConstructor
(new ResolvedParameter<Person>("Person-Singleton")));
myContainer.Resolve<IEmployee>("EmployeeWithPersonSingleton");
// Person (singleton)
myContainer.RegisterType<IPerson, Person>
("Person-Singleton", new ExternallyControlledLifetimeManager());
myContainer.Resolve<IPerson>("Person-Singleton");
// Person (non-singleton)
myContainer.RegisterType<IPerson, Person>("Person-Non-Singleton");
myContainer.Resolve<IPerson>("Person-Non-Singleton");
outMessage = "Populated Unity Container from the Application object.\n";
}
catch (Exception ex)
{
outMessage = "Error: Unity Container not populated correctly.
\n Details: " + ex.Message + "\n";
}
点击事件
private void btnNewEmployee_Click(object sender, RoutedEventArgs e){
IEmployee myEmployeeInstance = myContainer.Resolve<IEmployee>
("Person-Non-Singleton"); // resolve an instance of the class
// registered for IEmployee
this.txtblkOutput.Text += myEmployeeInstance.myPerson.Print(); // display the
// result of calling the class method
}
当用户点击按钮“创建带非单例 Person 类的‘新 Employee 类’”时,会创建一个新的 employee 类,其中包含一个非单例(映射)的“Person
”类。如果您打印 person 的年龄,它将始终为 0
,因为它将是一个新实例化的“Person
”类。
以下代码片段将打印“Person
”的 age 属性(如果未更新,则为 0
),但如果您更新该值然后再次单击此按钮,它将显示更新后的 person 的年龄(即使我们创建了“Employee
”类的新实例——弱耦合)。
private void btnNewEmployeeSingleton_Click(object sender, RoutedEventArgs e)
{
IEmployee myEmployeeInstance =
myContainer.Resolve<IEmployee>("EmployeeWithPersonSingleton");
this.txtblkOutput.Text += myEmployeeInstance.myPerson.Print();
}
此方法将执行与上述方法相同的操作,但这只是为了证明创建了“Employee
”的新实例,但使用单例生命周期管理器进行“Person
”类(因此您将看到更新后的 age 属性被显示)。
private void btnCurrentEmployeeSingleton_Click(object sender, RoutedEventArgs e)
{
IEmployee myEmployeeInstance = myContainer.Resolve<IEmployee>
("EmployeeWithPersonSingleton");
this.txtblkOutput.Text +=
myEmployeeInstance.myPerson.Print(); // should be the value you entered.
}
最后,update
方法将更改“Person
”类的 age
属性。当它以单例存在创建,并在之后以单例实例引用时,即使初始“Person
”类实例不存在或超出范围,您也可以查看更新后的值。
private void btnUpdate_Click(object sender, RoutedEventArgs e)
{
IEmployee myEmployeeInstance = myContainer.Resolve<IEmployee>
("EmployeeWithPersonSingleton");
try
{
myEmployeeInstance.myPerson.Age = Convert.ToInt32(this.txtNewValue.Text);
}
catch (Exception)
{
this.txtblkOutput.Text += "Please enter an integer.";
}
}
类
“Employee
”类是始终在我们的应用程序中实例化的类,但通过使用构造函数注入方法,我们能够创建“Person
”类。
using NotificationLibrary.Interfaces;
namespace NotificationLibrary.Model
{
public class Employee : NotificationLibrary.Interfaces.IEmployee
{
public IPerson myPerson { set; get; }
public Employee(Person baseObject)
{
myPerson = baseObject;
}
public string Print() { return "Hello from an employee.\n"; }
}
}
person
类有一个名为“Age
”的属性,我们可以更新它,然后在应用程序稍后通过单击其他单例按钮来查看此属性,以查看之前保存的 age
值。
namespace NotificationLibrary.Model
{
public class Person : NotificationLibrary.Interfaces.IPerson
{
private int age;
public int Age
{
get { return this.age; }
set { this.age = value; }
}
public Person() { this.Age = 0; }
public string Print() { return "Hello from a person that is
constructor injected into this onject. My age is " + this.Age.ToString() + "\n"; }
}
}
Interfaces
using System;
using NotificationLibrary.Model;
namespace NotificationLibrary.Interfaces
{
public interface IEmployee
{
IPerson myPerson { set; get; }
string Print();
}
}
using System;
namespace NotificationLibrary.Interfaces
{
public interface IPerson
{
string Print();
int Age { set; get; }
}
}
屏幕截图
