鄙人才疏学浅,若有不到之处,请多指教。
最近对依赖注入突然产生了浓厚的兴趣,所以决定搜索一些资料然后自己消化一下,并稍作整理,一来以后能够翻阅温习,二来能够为初识“依赖注入”的童鞋做一些参考。闲话少说,开始正题。
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风格后的运行结果
当然我们也可以利用反射来简化工厂中的创建实例的方法。具体的不在这里说了。