控制反转和依赖注入
转自:https://blog.csdn.net/zccai1205/article/details/51759319
经常听到这两个概念,感觉深不可测,其实就在实际运用之中
依赖注入
设计模式中,经常用到变依赖具体为依赖抽象,形成抽象类,接口,这样就摆脱了依赖的具体类型。
在这个过程中,怎么选择客户程序需要满足的抽象类型的具体类型呢?
在一个类中我们有时候需要访问其它的类的实例这个时候就需要使用依赖注入了
1:获取当前的系统时间的对象
using System; using System.Diagnostics; namespace MarvellousWorks.PracticalPattern.Concept.DependencyInjection.Example1 { class TimeProvider { public DateTime CurrentDate { get { return DateTime.Now; } } } public class Client { public int GetYear() { TimeProvider timeProvier = new TimeProvider(); return timeProvier.CurrentDate.Year; } } }
2:现在需要根据不同的精度客户端可以选择使用不同的方式,于是出现了抽象接口
using System; namespace MarvellousWorks.PracticalPattern.Concept.DependencyInjection.Example2 { interface ITimeProvider { DateTime CurrentDate { get;} } class TimeProvider : ITimeProvider { public DateTime CurrentDate { get { return DateTime.Now; } } } public class Client { public int GetYear() { ITimeProvider timeProvier = new TimeProvider(); return timeProvier.CurrentDate.Year; } } }
3:这样客户端处理时都依赖余抽象的ITimeProvider,需要只懂啊具体的SystemTimeProvider。所以需要增加一个对象,来选择将ITimeProvide的实例传递给客户端
public class Assembler { /// /// 保存“抽象类型/实体类型”对应关系的字典 /// private static Dictionary dictionary = new Dictionary(); static Assembler() { // 注册抽象类型需要使用的实体类型 // 实际的配置信息可以从外层机制获得,例如通过配置定义 dictionary.Add(typeof(ITimeProvider), typeof(SystemTimeProvider)); } /// 根据客户程序需要的抽象类型选择相应的实体类型,并返回类型实例 /// 实体类型实例 public object Create(Type type) // 主要用于非泛型方式调用 { if ((type == null) || !dictionary.ContainsKey(type)) throw new NullReferenceException(); Type targetType = dictionary[type]; return Activator.CreateInstance(targetType); } /// 抽象类型(抽象类/接口/或者某种基类) public T Create() // 主要用于泛型方式调用 { return (T)Create(typeof(T)); } }
4:Constructor注入 ,构造函数注入
class Client { private ITimeProvider timeProvider; public Client(ITimeProvider timeProvider) { this.timeProvider = timeProvider; } } Unit Test [TestClass] public class TestClient { [TestMethod] public void Test() { ITimeProvider timeProvider = (new Assembler()).Create(); Assert.IsNotNull(timeProvider); // 确认可以正常获得抽象类型实例 Client client = new Client(timeProvider); // 在构造函数中注入 } }
5:属性赋值注入
class Client { private ITimeProvider timeProvider; public ITimeProvider TimeProvider { get { return this.timeProvider; } // getter本身和以Setter方式实现注入没有关系 set { this.timeProvider = value; } // Setter } } Unit Test [TestClass] public class TestClient { [TestMethod] public void Test() { ITimeProvider timeProvider = (new Assembler()).Create(); Assert.IsNotNull(timeProvider); // 确认可以正常获得抽象类型实例 Client client = new Client(); client.TimeProvider = timeProvider; // 通过Setter实现注入 } }
6:接口注入
接口注入将包含抽象类型注入的入口以方法的形式定义在一个接口里面,如果客户类型需要实现这个注入的过程,则实现这个接口,不是很推荐
/// 定义需要注入ITimeProvider的类型 interface IObjectWithTimeProvider { ITimeProvider TimeProvider { get;set;} } /// 通过接口方式注入 class Client : IObjectWithTimeProvider { private ITimeProvider timeProvider; /// IObjectWithTimeProvider Members public ITimeProvider TimeProvider { get { return this.timeProvider; } set { this.timeProvider = value; } } } Unit Test [TestClass] public class TestClient { [TestMethod] public void Test() { ITimeProvider timeProvider = (new Assembler()).Create(); Assert.IsNotNull(timeProvider); // 确认可以正常获得抽象类型实例 IObjectWithTimeProvider objectWithTimeProvider = new Client(); objectWithTimeProvider.TimeProvider = timeProvider; // 通过接口方式注入 } }
7:基于Attribute实现注入---Attributer
果做个归纳,Martin Fowler之前所介绍的三种模式都是在对象部分进行扩展的,随着语言的发展(.NET从1.0开始,Java从5开始),很多在类元数据层次扩展的机制相继出现,如c#可以通过Attribute将附加的内容注入到对象上。直观上的客户对象有可能在使用上做出让步以适应这种变化,但这违背了依赖注入的初衷,三个角色(客户对象,Assembler,抽象类型)之中两个不能变,那只好在Assembler上下功夫了。
为了实现上的简洁,上面三个经典实现方式实际将抽象对象注入到客户类型都是在客户程序里面完成的,其实也可以通过Assembler完成,对于Attributer方式注入,最简单的方式则是直接吧实现了抽象类型的Attribute定义在客户类型上
如错误的实现情景
class SystemTimeAttribute : Attribute, ITimeProvider { … }
[SystemTime]
class Client { … }
这样可以让SystemTimeAttribute 获得了 ITimeProvider ,但是却失去了解耦的思想了,参考上面的思路
当抽象类型和客户算耦合的时候我们引入了Assembler,当Attribute方式出现类似的情况是,我们需要借用AttributeAssembler, 还不行。设计上要把Attribute设计成一个“通道”,考虑到扩展和通用性,它本身要协助AttributeAssembler完成ITimeProvider的装配,最好还可以同时装载其他抽象类型来修饰客户类型。
示例代码如下:
[AttributeUsage(AttributeTargets.Class, AllowMultiple=true)] sealed class DecoratorAttribute : Attribute { /// 实现客户类型实际需要的抽象类型的实体类型实例,即得注入客户类型的内容 public readonly object Injector; private Type type; public DecoratorAttribute(Type type) { if (type == null) throw new ArgumentNullException("type"); this.type = type; Injector = (new Assembler()).Create(this.type); } /// 客户类型需要的抽象对象类型 public Type Type { get { return this.type; } } } /// 帮助客户类型和客户程序获取其Attribute定义中需要的抽象类型实例的工具类 static class AttributeHelper { public static T Injector(object target) where T : class { if (target == null) throw new ArgumentNullException("target"); Type targetType = target.GetType(); object[] attributes = targetType.GetCustomAttributes( typeof(DecoratorAttribute), false); if ((attributes == null) || (attributes.Length <= 0))
return null ; foreach (DecoratorAttribute attribute in (DecoratorAttribute[])attributes) if (attribute.Type == typeof(T)) return (T)attribute.Injector; return null; } } [Decorator(typeof(ITimeProvider))] // 应用Attribute,定义需要将ITimeProvider通过它注入 class Client { public int GetYear() {
// 与其他方式注入不同的是,这里使用的ITimeProvider来自自己的Attribute ITimeProvider provider = AttributeHelper.Injector(this); return provider.CurrentDate.Year; } } Unit Test [TestMethod] public void Test() { Client client = new Client(); Assert.IsTrue(client.GetYear() > 0); }
依赖注入虽然被Martin Fowler称为一个模式,但平时使用中,它更多地作为一项实现技巧出现,开发中很多时候需要借助这项技巧把各个设计模式所加工的成果传递给客户程序
Constructor方式:它的注入是一次性的,当客户类型构造的时候就确定了。它很适合那种生命期不长的对象,比如在其存续期间不需要重新适配的对象。此外,相对Setter方式而言,在实现上Constructor可以节省很多代码;
l Setter方式:一个很灵活的实现方式,对于生命期较长的客户对象而言,可以在运行过程中随时适配;
l 接口方式:作为注入方式具有侵入性,很大程度上它适于需要同时约束一批客户类型的情况;
l 属性方式:随着开发语言的发展引入的新方式,它本身具有范围较小的固定内容侵入性(一个DecoratorAttribute),它也很适合需要同时约束一批客户类型情景。它本身实现相对复杂一些,但客户类型使用的时候非常方便——“打标签”即可。
关于控制反转