2.DI的实现方式
和上面的图1对应的是,如果我们的系统实现了依赖注入,组件间的依赖关系就变成了图2:
图2
说白了,就是要提供一个容器,由容器来完成(1)具体ServiceProvider的创建(2)ServiceUser和ServiceProvider的运行时绑定。下面我们就依次来看一下三种典型的依赖注入方式的实现。特别要说明的是,要理解依赖注入的机制,关键是理解容器的实现方式。本文后面给出的容器参考实现,均为黄忠成老师的代码,笔者仅在其中加上了一些关键注释而已。
2.1 Constructor Injection(构造器注入)
我们可以看到,在整个依赖注入的数据结构中,涉及到的重要的类型就是ServiceUser, ServiceProvider和Assembler三者,而这里所说的构造器,指的是ServiceUser的构造器。也就是说,在构造ServiceUser实例的时候,才把真正的ServiceProvider传给他:
1
class MovieLister
2

{
3
//其他内容,省略
4
5
public MovieLister(MovieFinder finder)
6
{
7
this.finder = finder;
8
}
9
}
接下来我们看看Assembler应该如何构建:
1
private MutablePicoContainer configureContainer()
{
2
MutablePicoContainer pico = new DefaultPicoContainer();
3
4
//下面就是把ServiceProvider和ServiceUser都放入容器的过程,以后就由容器来提供ServiceUser的已完成依赖注入实例,
5
//其中用到的实例参数和类型参数一般是从配置档中读取的,这里是个简单的写法。
6
//所有的依赖注入方法都会有类似的容器初始化过程,本文在后面的小节中就不再重复这一段代码了。
7
Parameter[] finderParams =
{new ConstantParameter("movies1.txt")};
8
pico.registerComponentImplementation(MovieFinder.class, ColonMovieFinder.class, finderParams);
9
pico.registerComponentImplementation(MovieLister.class);
10
//至此,容器里面装入了两个类型,其中没给出构造参数的那一个(MovieLister)将依靠其在构造器中定义的传入参数类型,在容器中
11
//进行查找,找到一个类型匹配项即可进行构造初始化。
12
return pico;
13
}
需要在强调一下的是,依赖并未消失,只是延后到了容器被构建的时刻。所以正如图2中您已经看到的,容器本身(更准确的说,是一个容器运行实例的构建过程)对ServiceUser和ServiceProvoder都是存在依赖关系的。所以,在这样的体系结构里,ServiceUser、ServiceProvider和容器都是稳定的,互相之间也没有任何依赖关系;所有的依赖关系、所有的变化都被封装进了容器实例的创建过程里,符合我们对服务应用的理解。而且,在实际开发中我们一般会采用配置文件来辅助容器实例的创建,将这种变化性排斥到编译期之外。
即使还没给出后面的代码,你也一定猜得到,这个container类一定有一个GetInstance(Type t)这样的方法,这个方法会为我们返回一个已经注入完毕的MovieLister。 一个简单的应用如下:
1
public void testWithPico()
2

{
3
MutablePicoContainer pico = configureContainer();
4
MovieLister lister = (MovieLister) pico.getComponentInstance(MovieLister.class);
5
Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
6
assertEquals("Once Upon a Time in the West", movies[0].getTitle());
7
}
上面最关键的就是对pico.getComponentInstance的调用。Assembler会在这个时候调用MovieLister的构造器,构造器的参数就是当时通过pico.registerComponentImplementation(MovieFinder.class, ColonMovieFinder.class, finderParams)设置进去的实际的ServiceProvider--ColonMovieFinder。下面请看这个容器的参考代码:

构造注入所需容器的伪码
1
public static class Container
2

{
3
private static Dictionary<Type, object> _stores = null;
4
5
private static Dictionary<Type, object> Stores
6
{
7
get
8
{
9
if (_stores == null)
10
_stores = new Dictionary<Type, object>();
11
return _stores;
12
}
13
}
14
15
private static Dictionary<string, object> CreateConstructorParameter(Type targetType)
16
{
17
Dictionary<string, object> paramArray = new Dictionary<string, object>();
18
19
ConstructorInfo[] cis = targetType.GetConstructors();
20
if (cis.Length > 1)
21
throw new Exception("target object has more then one constructor,container can't peek one for you.");
22
23
foreach (ParameterInfo pi in cis[0].GetParameters())
24
{
25
if (Stores.ContainsKey(pi.ParameterType))
26
paramArray.Add(pi.Name, GetInstance(pi.ParameterType));
27
}
28
return paramArray;
29
}
30
31
public static object GetInstance(Type t)
32
{
33
if (Stores.ContainsKey(t))
34
{
35
ConstructorInfo[] cis = t.GetConstructors();
36
if (cis.Length != 0)
37
{
38
Dictionary<string, object> paramArray = CreateConstructorParameter(t);
39
List<object> cArray = new List<object>();
40
foreach (ParameterInfo pi in cis[0].GetParameters())
41
{
42
if (paramArray.ContainsKey(pi.Name))
43
cArray.Add(paramArray[pi.Name]);
44
else
45
cArray.Add(null);
46
}
47
//在这里完成了对构造函数的调用,而构造函数的传入参数是通过在容器中查找匹配类型的实例得到的,
48
//所以被称为构造器注入。
49
return cis[0].Invoke(cArray.ToArray());
50
}
51
else if (Stores[t] != null)
52
return Stores[t];
53
else
54
return Activator.CreateInstance(t, false);
55
}
56
return Activator.CreateInstance(t, false);
57
}
58
59
//向容器中加入ServiceProvider的实例
60
public static void RegisterImplement(Type t, object impl)
61
{
62
if (Stores.ContainsKey(t))
63
Stores[t] = impl;
64
else
65
Stores.Add(t, impl);
66
}
67
68
//向容器中加入ServiceUser的类型,类型的构造器将在容器中被调用
69
public static void RegisterImplement(Type t)
70
{
71
if (!Stores.ContainsKey(t))
72
Stores.Add(t, null);
73
}
74
}