目录
介绍
控制反转(IoC)
什么是控制反转
接口反转
流反转
创建反转
依赖注入
总结
介绍
这是有关依赖反转原理,IoC容器和依赖注入的文章的继续部分。在本文的上半部分,我解释了什么是依赖反转原理及其好处。在本文的这一部分中,我将介绍控制反转(IoC)和什么是IoC容器。
以下是本文不同部分的清单:
第1部分:依赖倒置原则第2部分:控制反转和IoC容器(当前正在阅读)第3部分:自定义IoC容器第4部分:具有生命周期选项的自定义IoC容器第5部分:使用Microsoft Unity的依赖项注入(DI)
控制反转(IoC)
什么是控制反转
许多人对控制反转有不同的看法。您可以在不同的应用程序上下文中具有不同的含义。基本上,这是通过切换到另一个进行控制的位置来反转对依赖性的控制的模式。
DIP认为高级模块不应该依赖于低级模块,高级模块和低级模块都应该依赖于抽象。如果要防止在更改低层模块时更改高层模块,则需要反转控件,以使低层模块不会控制高层模块所需的接口和对象的创建。IoC是提供这种抽象的模式。
在下面的示例(源自上一篇文章)中,我们通过将用于创建依赖项的代码切换到服务定位器(以粗体字母标记)来反转依赖项对象(读取器和写入器)的创建。
public class Copy{public void DoWork(){IReader reader = serviceLocator.GetReader();IWriter writer = serviceLocator.GetWriter();string data = reader.Read();writer.Write(data);}}
这是控制反转的例子之一。可以有多种方法来反转控件。但是在当前的软件开发趋势中,有3种反转控制的基本方法。
接口反转流反转创建反转
接口反转
这是一种非常常见的反转。我已经在本文的第一部分中讨论了一些有关接口反转的内容。通过为我们所有的底层模块提供抽象,并不意味着我们已经实现了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 interface。在这种情况下,高级类不应理会包含引用的实例。它只是借助接口来调用Move()方法。
流反转
流反转讨论了反转执行流。例如,在早期的基于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, nValue2, nSum));Console.ReadKey();}
以下是上述程序的输出:
在这里,您只能在输入value1值之后输入值value2。因此,value2的输入取决于value1的输入。
但是,如果您查看下面的GUI应用程序,则可以看到我们如何反转流程以消除依赖关系。
在这里,执行流程是反向的,并且现在两者都是输入,value1并且value2彼此独立。
创建反转
在创建反转中,将依赖对象的创建从高级模块转换到其他位置。
public class Copy{public void DoWork(){IReader reader = serviceLocator.GetReader();IWriter writer = serviceLocator.GetWriter();string data = reader.Read();writer.Write(data);}}
在上面的程序中,我们将IReader和IWriter对象的创建转换为serviceLocator。Object创造责任被赋予serviceLocator。而且Copy程序对实例没有任何想法。因此,对于任何增加新的实现类的IReader和IWriter,也没有必要修改的Copy程序。
IoC容器还可以使用其他方式来实现创建反转。IoC容器包含每个抽象及其实现的配置设置。每当需要抽象实例时,IoC容器都会将其提供给请求者。
在图1.a中,实现了接口反转,但是仍然存在依赖关系,因此高级模块和低级模块之间存在紧密耦合。但是在图1.b中,这种依赖性通过IoC容器的实现得以解决。
将依赖注入到高级模块(消费者)的机制称为“依赖注入(DI)”。DI使用IoC容器来解决依赖关系。
依赖注入
如上所述,DI是将依赖(低级实例)注入高级模块的机制,DI使用IoC容器来保存与依赖项有关的配置设置,并根据请求解析抽象。
在.NET中,有许多IoC容器提供了根据请求注入依赖项的工具。下面列出了一些最受欢迎的IoC容器:
Unity容器Castle WindsorNInject结构映射 (Structure Map)
在本文的下一部分中,我将解释如何实现我们自己的自定义IoC容器,以及实现依赖注入以解决与基于分层的应用程序相关的许多问题的方法。
总结
在本文的这一部分中,我试图解释什么是控制反转及其如何解决紧密耦合。希望您发现这个主题很好并且易于理解。