概述
前文中介绍了依赖倒置原则及其优点,本文我将会介绍控制反转(Inversion Of Control, IoC)及控制反转容器(IoC Container)。
本系列分为5个部分。
1. 依赖倒置原则
2. 控制反转和控制反转容器(IoC Container)
3. 自定义控制反转容器(IoC Container)
4. 自定义支持生命周期的控制反转容器(IoC Container)
5. 利用Microsoft Unity的依赖注入(Dependency Injection)
控制反转(Inversion of Control,IoC)
什么是控制反转
很多人对控制反转有不同的理解,在不同的应用程序场景下,确实有不同的理解。但是其根本思想是通过将控制逻辑转移至其他位置,实现依赖的控制反转。
依赖倒置原则(DIP)中要求上层模块不应该依赖底层模块,并且无论是上层模块还是底层模块都应该依赖抽象。如果你想在修改底层模块时对上层模块没有影响,不用因为底层模块的修改而修正上层模块,首先要保证底层模块不会变更上层模块需要的接口及底层模块实例化操作。控制反转就是这一设计模式,来实现上述抽象。
在下面的代码中,我们实现了对于创建依赖对象的反转,主要是通过将创建依赖对象的代码调整到service locator中。
public class Copy { public void DoWork() { IReader reader = serviceLocator.GetReader(); IWriter writer = serviceLocator.GetWriter(); string data = reader.Read(); writer.Write(data); } }
有很多种方法来实现控制反转,但是在当前的软件开发趋势中,有三种基本的方法来实现控制反转。接口反转(Interface Inversion)、流反转(Flow Inversion)和创建反转(Creation Inversion)。
接口反转(InterfaceInversion)
接口反转是最常见的反转方法,在本系列文章一中已经介绍了。但是仅仅是提供底层模块的抽象接口并不意味着我们已经实现了依赖倒置原则(DIP)。
例如如下代码,两个底层模块“Car”和“Bicycle”都拥有其自身的抽象接口。
public interface ICar { void Run(); } public class Car : ICar { public void Run() { .... } } public interface IBicycle { void Move(); } public class Bicycle : IBicycle { public void Move() { .... } }
在上述代码中,虽然我们定义了抽象的接口,但仍然有问题,就是每一个底层模块拥有各自不同的抽象接口,当高层模块与接口交互时,高层模块需要将不同模块实现的接口区别对待。例如上述代码,高层模块操作“ICar”的对象时,需要调用Run函数,操作“IBicycle”的对象时,又需要调用Move函数。因此高层对象需要知道每一个底层对象所实现的接口。
但是,接口反转要求高层模块应该定义接口,底层模块来继承这些接口。因此接口反转的使用应该如下所示:
public interface IVehicle { void Move(); } public class Car : IVehicle { public void Move() { .... } } public class Bicycle : IVehicle { public void Move() { .... } }
上述代码中类“Car”和“Bicycle”都实现了IVehicle接口,这一接口由高层模块定义。在这种情况下,高层模块不必关心具体实例,仅仅是调用接口的Move方法即可。
流反转(Flow Inversion)
流反转说的是指的是执行顺序的反转,例如,早期的基于DOS的程序,这种程序只能顺序的读取用户输入,因此你只有输入完一个数据的话,才能输入下一个数据,然后再输入第三个数据。如下是基于C#的控制台程序。
static void Main(string[] args) { int nValue1,nValue2,nSum; Console.Write("Enter value1 :"); nValue1 = Convert.ToInt32(Console.ReadLine()); Console.Write("Enter value2 :"); nValue2 = Convert.ToInt32(Console.ReadLine()); nSum = nValue1 + nValue2; Console.WriteLine(string.Format("The sum of {0} and {1} is {2}",nValue1,nSum)); Console.ReadKey(); }
在上述程序中,只能先输入第一个数值给nValue1,然后才能输入第二个数值给nValue2,因此输入第二个数值是依赖第一个数值的。
但是如果将上述求和的功能利用GUI程序来实现,这种第一个数据值和第二个数据值的依赖关系就没有了。
创建反转(CreationInversion)
创建反转指的是,原来有高层模块创建依赖对象的逻辑改由其他地方来创建了。
public class Copy { public void DoWork() { IReader reader = serviceLocator.GetReader(); IWriter writer = serviceLocator.GetWriter(); string data = reader.Read(); writer.Write(data); } }
在上述代码中,我们将创建IReader和IWriter对象的方法放在了serviceLocator中,这样Copy程序就不用管接口背后的具体类了。代码重构到这种情况,当IReader和IWriter添加新的执行处理时,对于Copy程序来说没有任何的影响,不必修改Copy程序。
IoC容器还有另外一种方式来执行创建反转,IoC容器中包含了每一个抽象接口及其接口实现间的配置关系,当需要创建一个接口实例时,IoC容器会创建实例并返回给请求者。
Figure 1
图1中,接口反转仍然存在依赖,上层模块与底层模块之间仍然存在紧耦合的关系。在图2中,依赖关系被彻底的解除掉了。
Figure 2
将依赖注入到上层模块的机制被称为依赖注入(DependencyInjection),依赖注入就是利用IoC容器来解除依赖关系的。
依赖注入(Dependency Injection)
依赖注入是一种机制,实现将依赖(底层实例)注入到上层模块中,并且依赖注入是要使用IoC容器的。IoC容器内部维护了接口和实现的配置关系。
在.NET中,有需要IoC容器支持依赖注入,比较常用的IoC容器如下所示:
(1) Unity Container
(2) Castle Windsor
(3) NInject
(4) Structure Map
在下一篇文章中,我来解释如何实现我们自己的IoC容器及利用依赖注入来解决实际中遇到的一些问题。
小结
本文介绍了控制反转原则,及其如何来解决紧耦合的问题,希望对大家带来帮助。
依赖倒置原则,及其在真实的使用场景。在后面的文章中我会介绍控制反转(Inversionof Control,IoC)和依赖注入(DependencyInjection,DI)。
原文地址
http://www.codeproject.com/Articles/542752/Dependency-Inversion-Principle-IoC-Container-Depen