一、什么是Ioc
IoC(Inverse of Control)的字面意思是控制反转,它包括两个内容: 控制、反转
可以假设这样一个场景:火车运货,不同类型的车厢运送不同类型的货物,板车运送圆木,罐车运送柴油,箱车运送水果。那么对于运送货物这件事,需是列车挂不同的车厢运送货物。显然列车和运送货物之间是有依赖关系的(控制:依赖关系)。我们把列车挂什么样的车厢交给调度中心,而不是交给列车决定,这就形成了依赖反转。
因为IoC确实不够开门见山,因此业界曾进行了广泛的讨论,最终软件界的泰斗级人物Martin Fowler提出了DI(依赖注入:Dependency Injection)的概念用以代替IoC,即让调用类对某一接口实现类的依赖关系由第三方(容器或协作类)注入,以移除调用类对某一接口实现类的依赖。“依赖注入”这个名词显然比“控制反转”直接明了、易于理解。
依赖注入和控制反转是同一概念吗?
根据上面的讲述,应该能看出来,依赖注入和控制反转是对同一件事情的不同描述,从某个方面讲,就是它们描述的角度不同。依赖注入是从应用程序的角度在描述,可以把依赖注入描述完整点:应用程序依赖容器创建并注入它所需要的外部资源;而控制反转是从容器的角度在描述,描述完整点:容器控制应用程序,由容器反向的向应用程序注入应用程序所需要的外部资源。
其实IoC/DI对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC/DI容器来创建并注入它所需要的资源了。
这么小小的一个改变其实是编程思想的一个大进步,这样就有效的分离了对象和它所需要的外部资源,使得它们松散耦合,有利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
举个例子:有一个闹钟会问早安,当它地理位置定位在中国的时候,它会问候:早安;当在美国的时候,它会问候:Good Morning;那么我们把闹钟看做一个客户,问候看做一个服务,这个闹钟是依赖于服务的。闹钟遵从OCP原则应该有一个SayMorning的注入点,而问候就需要使用策略模式列出。实现这个功能:
public interface ISayMorning { void SayMorning(); } public class ChinesePosition:ISayMorning { void SayMorning() { Console.Writeline("早安!"); } } public class EnglishPosition:ISayMorning { void SayMorning() { Console.Writeline("GoodMorning!"); } } //下面创建客户 public class ClockClient() { public ISayMorning SayService{set;} public Set_Sayservices(ISayMorning sayService) { SayService=sayService; } public SayMorning() { SayService.SayMorning(); } } //主函数中 var clock=new ClockClient(); var saysChinese=new ChinesePosition(); clock.Set_Sayservices(saysChinese); clock.SayMorning();
以上,闹钟说话依赖于说话服务,说话服务有很多策略(算法),我们将服务注入(DL)客户,将客户的依赖项(服务)反转到对象创建后再根据类别注入选择,这就形成了依赖反转(IOC)。这样做的的好处是策略变化(地区),我们只要新建类就好了
而不用修改已经写好的代码,实现了OCP(设计模式遵循的六大原则之 开闭原则)。
二、几个相似相关的概念
依赖倒置原则(DIP):一种软件架构设计的原则(抽象概念)。
控制反转(IoC):一种反转流、依赖和接口的方式(DIP的具体实现方式)。
依赖注入(DI):IoC的一种实现方式,用来反转依赖(IoC的具体实现方式)。
IoC容器:依赖注入的框架,用来映射依赖,管理对象创建和生存周期(DI框架)。
三、Ioc的类型
IOC的实现有三种方式
构造注入:就是将开头例子中的Setter方式,在创建客户对象的时候,初始化进入。
Setter注入:就是开头的例子。
依赖获取。就是在注入的时候,利用一下虚拟工厂(Abstract Factory),这种适用于服务不仅一种的时候,比如我们赋予闹钟报时功能。
1、构造函数注入
public Class SayHello { private IPeople _people; public SayHello(IPeople p) { _people=p; } public void Say() { _people.Say(); } }
2、属性注入
using Microsoft.VisualStudio.TestTools.UnitTesting; using VisionLogic.Training.DependencyInjection.Scenario; namespace VisionLogic.Training.DependencyInjection.Scenario.UnitTest { [TestClass] public class SetterInjectionTest { class Client { private IWeatherReader reader; public IWeatherReader Reader { get { return reader; } set { reader = value; } } } [TestMethod] public void Test() { IWeatherReader reader = new Assembler<IWeatherReader>().Create(); Client client = new Client(); client.Reader = reader; Assert.IsNotNull(client.Reader); } } }
也可以写一个Setter方法
3、接口注入
using Microsoft.VisualStudio.TestTools.UnitTesting; using VisionLogic.Training.DependencyInjection.Scenario; namespace VisionLogic.Training.DependencyInjection.Scenario.UnitTest { [TestClass] public class InterfaceInjectionTest { interface IClientWithWeatherReader { IWeatherReader Reader { get; set;} } class Client : IClientWithWeatherReader { private IWeatherReader reader; #region IClientWithWeatherReader Members public IWeatherReader Reader { get { return reader; } set { reader = value; } } #endregion } [TestMethod] public void Test() { IWeatherReader reader = new Assembler<IWeatherReader>().Create(); Client client = new Client(); IClientWithWeatherReader clientWithReader = client; clientWithReader.Reader = reader; Assert.IsNotNull(clientWithReader.Reader); } } }
4、依赖获取
public interface ISayMorning { void SayMorning(); } public class ChinesePosition:ISayMorning { void SayMorning() { Console.Writeline("早安!"); } } public class EnglishPosition:ISayMorning { void SayMorning() { Console.Writeline("GoodMorning!"); } } public interface IFactory { ISayTime MakeTimeSayer(); ISayMoring MakeMorningSayer(); } public class FactoryAmerican:IFactory { public ISayTime MakeTimeSayer() { return new EnglishPositionTime(); //未实现 } public ISayMoring MakeMorningSayer() { return new EnglishPosition(); } } //位于中国的工厂 public class FactoryChinese:IFactory { public ISayTime MakeTimeSayer() { return new ChinesePositionTime(); //未实现 } public ISayMoring MakeMorningSayer() { return new ChinesePosition(); } } public statics class FactoryContainer { static FactoryContainer() { XmlDocument xmlDoc = new XmlDocument(); xmlDoc.Load("Config.xml"); XmlNode xmlNode =xmlDoc.ChildNodes[1].ChildNodes[0].ChildNodes[0]; if ("Chinese" == xmlNode.Value) { factory = new FactoryChinese(); } else if ("American" == xmlNode.Value) { factory = new FactoryAmerican(); } else { throw new Exception("Factory Init Error"); } } } } //调用 IFactory factory = FactoryContainer.factory; IWindow window = factory.SayMorning();
这样,我们可以用过xml文件配置工厂(也就是不同的定位条件)。
四、一个例子
C#
using System;
namespace VisionLogic.Training.DependencyInjection.Scenario.Attributer
{
/// <summary>
/// 抽象的处理对象
/// </summary>
public interface IObjectWithGuid
{
string Guid { get; set;}
}
}
定义需要注入的限制接口,并用一个Attribute管理它
C#
using System;
namespace VisionLogic.Training.DependencyInjection.Scenario.Attributer
{
/// <summary>
/// 需要注入的用以限制最大数量的接口
/// </summary>
public interface ICapacityConstraint
{
int Max { get;}
}
public class CapacityConstraint : ICapacityConstraint
{
private int max;
public CapacityConstraint(){this.max = 0;} // 默认情况下不限制
public CapacityConstraint(int max) { this.max = max; }
public int Max { get { return max; } }
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class ConstraintAttribute : Attribute
{
private ICapacityConstraint capacity;
public ConstraintAttribute(int max) { this.capacity = new CapacityConstraint(max); }
public ConstraintAttribute() { this.capacity = null; }
public ICapacityConstraint Capacity { get { return capacity; } }
}
}
Assembler上增加通过Attribute注入限制的响应。
using System;
using System.Collections.Generic;
namespace VisionLogic.Training.DependencyInjection.Scenario.Attributer
{
public class Assembler
{
/// <summary>
/// 登记相关类型对“最大容量”属性的使用情况
/// </summary>
private IDictionary<Type, ConstraintAttribute> attributeRegistry = new Dictionary<Type, ConstraintAttribute>();
/// <summary>
/// 登记每个类型(如须受到“最大容量”属性限制的话),实际已经创建的对象数量
/// </summary>
private IDictionary<Type, int> usageRegistry = new Dictionary<Type, int>();
public T Create<T>() where T : IObjectWithGuid, new()
{
ICapacityConstraint constraint = GetAttributeDefinedMax(typeof(T));
if ((constraint == null) || (constraint.Max <= 0)) // max <= 0 代表是不需要限制数量的。
return InternalCreate<T>();
else
{
if (usageRegistry[typeof(T)] < constraint.Max) // 检查是否超出容量限制
{
usageRegistry[typeof(T)]++; // 更新使用情况注册信息
return InternalCreate<T>();
}
else
return default(T);
}
}
// helper method
// 直接生成特定实例,并setter 方式注入其guid。
private T InternalCreate<T>()
where T : IObjectWithGuid, new()
{
T result = new T();
result.Guid = Guid.NewGuid().ToString();
return result;
}
/// helper method.
// 获取特定类型所定义的最大数量, 同时视情况维护attributeRegistry 和usageRegistry 的注册信息。
private ICapacityConstraint GetAttributeDefinedMax(Type type)
{
ConstraintAttribute attribute = null;
if (!attributeRegistry.TryGetValue(type, out attribute)) //新的待创建的类型
{
// 填充相关类型的“最大容量”属性注册信息
object[] attributes = type.GetCustomAttributes(typeof(ConstraintAttribute), false);
if ((attributes == null) || (attributes.Length <= 0))
attributeRegistry.Add(type, null);
else
{
attribute = (ConstraintAttribute)attributes[0];
attributeRegistry.Add(type, attribute);
usageRegistry.Add(type, 0); // 同时补充该类型的使用情况注册信息
}
}
if (attribute == null)
return null;
else
return attribute.Capacity;
}
}
}
4.2对方案的测试
using Microsoft.VisualStudio.TestTools.UnitTesting;
using VisionLogic.Training.DependencyInjection.Scenario.Attributer;
namespace VisionLogic.Training.DependencyInjection.Scenario.UnitTest.Attributer
{
[TestClass()]
public class AssemblerTest
{
public abstract class ObjectWithGuidBase : IObjectWithGuid
{
protected string guid;
public virtual string Guid
{
get { return guid; }
set { guid = value; }
}
}
[Constraint(2)] // 通过属性注入限制
public class ObjectWithGuidImplA : ObjectWithGuidBase { }
[Constraint(0)] // 通过属性注入限制
public class ObjectWithGuidImplB : ObjectWithGuidBase { }
[Constraint(-5)] // 通过属性注入限制
public class ObjectWithGuidImplC : ObjectWithGuidBase { }
public class ObjectWithGuidImplD : ObjectWithGuidBase { }
[TestMethod]
public void Test()
{
Assembler assembler = new Assembler();
for (int i = 0; i < 2; i++)
Assert.IsNotNull(assembler.Create<ObjectWithGuidImplA>());
Assert.IsNull(assembler.Create<ObjectWithGuidImplA>()); // 最多两个
for (int i = 0; i < 100; i++)
Assert.IsNotNull(assembler.Create<ObjectWithGuidImplB>()); // 不限制
for (int i = 0; i < 100; i++)
Assert.IsNotNull(assembler.Create<ObjectWithGuidImplC>()); // 不限制
for (int i = 0; i < 100; i++)
Assert.IsNotNull(assembler.Create<ObjectWithGuidImplD>()); // 不限制
}
}
}