鄙人才疏学浅,若有不到之处,请多指教。
最近对依赖注入突然产生了浓厚的兴趣,所以决定搜索一些资料然后自己消化一下,并稍作整理,一来以后能够翻阅温习,二来能够为初识“依赖注入”的童鞋做一些参考。闲话少说,开始正题。
1.定义
说道“依赖注入”会联系到令一个词“控制反转”,其实他们说的都是一回事儿,以下摘自百度知道对“依赖注入的”定义:
控制反转(Inversion of Control,英文缩写为IoC)是一个重要的面向对象编程的法则来削减计算机程序的耦合问题。 控制反转还有一个名字叫做依赖注入(Dependency Injection)。简称DI。
通过定义我们也能理解到 IOC不是什么技术,与GOF一样,是一种设计模式。其目的就是降低耦合度。
2.理解
既然为“依赖注入”,那我们先搞懂什么是依赖。
简单的理解,就是一个类A使用到了令一个类B,而这种使用关系是具有偶然行的、临时性的、非常脆弱的,但是B类的变化会影响到A;也就是B作为参数被A在某个方法中使用。
现在编程比较流行的是接口驱动(面向接口编程),什么时候用接口呢?比如说B类会有多种的形态存在,但是基本行为都一样。那么B类就可以抽象出来一个接口IB,这样A就依赖于IB,也就是A依赖于接口。但是接口只是方法的签名,那么具体的实现还得是继承了IB的B1或B2,也就是所A应该能够调用具体的B1或者B2的方法,最终依赖的是B1或者B2。
代码如下:
//定义接口IB public interface IB { void Method(); } public class B1 : IB { public void Method() { Console.WriteLine("B1的Method"); } } public class B2 : IB { public void Method() { Console.WriteLine("B2的Method"); } } public class A { public IB instance; public A(IB b) { instance = b;//实例化IB } public void Method() { //调用IB的具体的实例的方法 instance.Method(); } }
这里描述了A和IB之间的依赖关系,在A类的构造函数中需要一个IB类型的参数,并且赋值给IB类型的instance属性,这样在A的方法中可以调用instance的方法。
客户端调用:
internal class Program { private static void Main(string[] args) { IB b1 = new B1(); A a = new A(b1);//将b1注入到了A中 a.Method(); } }从客户端调用的代码中不难看出,依赖注入的基本原理。通过A的构造函数传递IB的实例的过程就是依赖注入。当然我们也可以不借助构造函数,也可以给IB类型的属性构造访问器,在客户端直接访问属性进行设置。这样也完成了注入操作。
到此我们可以重新理解依赖注入的定义,因为之前的定义很模糊不够具体。
依赖注入的正式定义:
依赖注入(Dependency Injection),是这样一个过程:由于某客户类只依赖于服务类的一个接口,而不依赖于具体服务类,所以客户类只定义一个注入点。在程序运行过程中,客户类不直接实例化具体服务类实例,而是客户类的运行上下文环境或专门组件负责实例化服务类,然后将其注入到客户类中,保证客户类的正常运行。
3.依赖注入的类别
依赖注入的方式不同,之前也提到了,下边就正式定义一下依赖注入的类别。
3.1 构造注入
顾名思义,是通过客户类(A类)的构造函数,像客户类注入服务类(IB类)实例。
构造注入(Constructor Injection)是指在客户类中,设置一个服务类接口类型的数据成员,并以构造函数为注入点,这个构造函数接受一个具体的服务类实例为参数,并将它赋给服务类接口类型的数据成员。(上边的代码就用的构造注入)
3.2 Setter注入
Setter注入(Setter Injection)是指在客户类中,设置一个服务类接口类型的数据成员,并设置一个Set方法作为注入点,这个Set方法接受一个具体的服务类实例为参数,并将它赋给服务类接口类型的数据成员。
也就是我刚刚上边提到的 定义属性访问器来赋值给IB类型的属性的方式进行注入。
代码如下:
//定义接口IB(服务类) public interface IB { void Method(); } public class B1 : IB { public void Method() { Console.WriteLine("B1的Method"); } } public class B2 : IB { public void Method() { Console.WriteLine("B2的Method"); } } //客户类 public class A { private IB instance; public A() { } //注入点 public IB Set_instance { set { instance = value; } } public void Method() { //调用IB的具体的实例的方法 instance.Method(); } } internal class Program { //客户端调用 private static void Main(string[] args) { IB b1 = new B1(); A a = new A(); a.Set_instance = b1;//将b1注入到了A中 a.Method(); } }3.3 依赖获取
依赖获取(Dependency Locate)是指在系统中提供一个获取点,客户类仍然依赖服务类的接口。当客户类需要服务类时,从获取点主动取得指定的服务类,具体的服务类类型由获取点的配置决定。
依赖获取变被动为主动
具体代码如下(摘自张洋http://www.cnblogs.com/leoo2sk/archive/2009/06/17/1504693.html)
下面给一个具体的例子,现在我们假设有个程序,既可以使用Windows风格外观,又可以使用Mac风格外观,而内部业务是一样的。
图1依赖获取示意
上图乍看有点复杂,不过如果读者熟悉Abstract Factory模式,应该能很容易看懂,这就是Abstract Factory在实际中的一个应用。这里的Factory Container作为获取点,是一个静态类,它的“Type构造函数”依据外部的XML配置文件,决定实例化哪个工厂。下面还是来看示例代码。由于不同组件的代码是相似的,这里只给出Button组件的示例代码.
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DependencyLocate { internal interface IButton { String ShowInfo(); } } ? using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DependencyLocate { internal sealed class WindowsButton : IButton { public String Description { get; private set; } public WindowsButton() { this.Description = "Windows风格按钮"; } public String ShowInfo() { return this.Description; } } } ? using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DependencyLocate { internal sealed class MacButton : IButton { public String Description { get; private set; } public MacButton() { this.Description = " Mac风格按钮"; } public String ShowInfo() { return this.Description; } } } ? using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DependencyLocate { internal interface IFactory { IWindow MakeWindow(); IButton MakeButton(); ITextBox MakeTextBox(); } } ? using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DependencyLocate { internal sealed class WindowsFactory : IFactory { public IWindow MakeWindow() { return new WindowsWindow(); } public IButton MakeButton() { return new WindowsButton(); } public ITextBox MakeTextBox() { return new WindowsTextBox(); } } } ? using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DependencyLocate { internal sealed class MacFactory : IFactory { public IWindow MakeWindow() { return new MacWindow(); } public IButton MakeButton() { return new MacButton(); } public ITextBox MakeTextBox() { return new MacTextBox(); } } } ? using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Xml; namespace DependencyLocate { internal static class FactoryContainer { public static IFactory factory { get; private set; } static FactoryContainer() { XmlDocument xmlDoc = new XmlDocument(); xmlDoc.Load("http://www.cnblogs.com/Config.xml"); XmlNode xmlNode = xmlDoc.ChildNodes[1].ChildNodes[0].ChildNodes[0]; if ("Windows" == xmlNode.Value) { factory = new WindowsFactory(); } else if ("Mac" == xmlNode.Value) { factory = new MacFactory(); } else { throw new Exception("Factory Init Error"); } } } } ? using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DependencyLocate { class Program { static void Main(string[] args) { IFactory factory = FactoryContainer.factory; IWindow window = factory.MakeWindow(); Console.WriteLine("创建 " + window.ShowInfo()); IButton button = factory.MakeButton(); Console.WriteLine("创建 " + button.ShowInfo()); ITextBox textBox = factory.MakeTextBox(); Console.WriteLine("创建 " + textBox.ShowInfo()); Console.ReadLine(); } } }
这里我们用XML作为配置文件。配置文件Config.xml如下:
<?xml version="1.0" encoding="utf-8" ?> <config> <factory>Mac</factory> </config>这里我们将配置设置为Mac风格,编译运行上述代码,运行结果如下:
图2 配置Mac风格后的运行结果
当然我们也可以利用反射来简化工厂中的创建实例的方法。具体的不在这里说了。