目录html
1 IGame游戏公司的故事算法
1.1 讨论会spring
1.2 实习生小李的实现方法数据库
1.3 架构师的建议编程
1.4 小李的小结设计模式
2 探究依赖注入架构
2.1 故事的启迪框架
2.2 正式定义依赖注入dom
3 依赖注入那些事儿ide
3.1 依赖注入的类别
3.1.1 Setter注入
3.1.2 Construtor注入
3.1.3 依赖获取
3.2 反射与依赖注入
3.3 多态的活性与依赖注入
3.3.1 多态性的活性
3.3.2 不一样活性多态性依赖注入的选择
4 IoC Container
4.1 IoC Container出现的必然性
4.2 IoC Container的分类
4.2.1 重量级IoC Container
4.2.2 轻量级IoC Container
4.3 .NET平台上典型IoC Container推介
4.3.1 Spring.NET
4.3.2 Unity
参考文献
话说有一个叫IGame的游戏公司,正在开发一款ARPG游戏(动做&角色扮演类游戏,如魔兽世界、梦幻西游这一类的游戏)。通常这类游戏都有一个基本的功能,就是打怪(玩家攻击怪物,借此得到经验、虚拟货币和虚拟装备),而且根据玩家角色所装备的武器不一样,攻击效果也不一样。这天,IGame公司的开发小组正在开会对打怪功能中的某一个功能点如何实现进行讨论,他们面前的大屏幕上是这样一份需求描述的ppt:
图1.1 需求描述ppt
各个开发人员,面对这份需求,展开了热烈的讨论,下面咱们看看讨论会上都发生了什么。
在通过一番讨论后,项目组长Peter以为有必要整理一下各方的意见,他首先询问小李的见解。小李是某学校计算机系大三学生,对游戏开发特别感兴趣,目前是IGame公司的一名实习生。
通过短暂的思考,小李阐述了本身的意见:
“我认为,这个需求能够这么实现。HP固然是怪物的一个属性成员,而武器是角色的一个属性成员,类型可使字符串,用于描述目前角色所装备的武器。角色类有一个攻击方法,以被攻击怪物为参数,当实施一次攻击时,攻击方法被调用,而这个方法首先判断当前角色装备了什么武器,而后据此对被攻击怪物的HP进行操做,以产生不一样效果。”
而在阐述完后,小李也飞快的在本身的电脑上写了一个Demo,来演示他的想法,Demo代码以下。
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
namespace
IGameLi
{
/// <summary>
/// 怪物
/// </summary>
internal
sealed
class
Monster
{
/// <summary>
/// 怪物的名字
/// </summary>
public
String Name {
get
;
set
; }
/// <summary>
/// 怪物的生命值
/// </summary>
public
Int32 HP {
get
;
set
; }
public
Monster(String name,Int32 hp)
{
this
.Name = name;
this
.HP = hp;
}
}
}
|
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
namespace
IGameLi
{
/// <summary>
/// 角色
/// </summary>
internal
sealed
class
Role
{
private
Random _random =
new
Random();
/// <summary>
/// 表示角色目前所持武器的字符串
/// </summary>
public
String WeaponTag {
get
;
set
; }
/// <summary>
/// 攻击怪物
/// </summary>
/// <param name="monster">被攻击的怪物</param>
public
void
Attack(Monster monster)
{
if
(monster.HP <= 0)
{
Console.WriteLine(
"此怪物已死"
);
return
;
}
if
(
"WoodSword"
==
this
.WeaponTag)
{
monster.HP -= 20;
if
(monster.HP <= 0)
{
Console.WriteLine(
"攻击成功!怪物"
+ monster.Name +
"已死亡"
);
}
else
{
Console.WriteLine(
"攻击成功!怪物"
+ monster.Name +
"损失20HP"
);
}
}
else
if
(
"IronSword"
==
this
.WeaponTag)
{
monster.HP -= 50;
if
(monster.HP <= 0)
{
Console.WriteLine(
"攻击成功!怪物"
+ monster.Name +
"已死亡"
);
}
else
{
Console.WriteLine(
"攻击成功!怪物"
+ monster.Name +
"损失50HP"
);
}
}
else
if
(
"MagicSword"
==
this
.WeaponTag)
{
Int32 loss = (_random.NextDouble() < 0.5) ? 100 : 200;
monster.HP -= loss;
if
(200 == loss)
{
Console.WriteLine(
"出现暴击!!!"
);
}
if
(monster.HP <= 0)
{
Console.WriteLine(
"攻击成功!怪物"
+ monster.Name +
"已死亡"
);
}
else
{
Console.WriteLine(
"攻击成功!怪物"
+ monster.Name +
"损失"
+ loss +
"HP"
);
}
}
else
{
Console.WriteLine(
"角色手里没有武器,没法攻击!"
);
}
}
}
}
|
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
namespace
IGameLi
{
class
Program
{
static
void
Main(
string
[] args)
{
//生成怪物
Monster monster1 =
new
Monster(
"小怪A"
, 50);
Monster monster2 =
new
Monster(
"小怪B"
, 50);
Monster monster3 =
new
Monster(
"关主"
, 200);
Monster monster4 =
new
Monster(
"最终Boss"
, 1000);
//生成角色
Role role =
new
Role();
//木剑攻击
role.WeaponTag =
"WoodSword"
;
role.Attack(monster1);
//铁剑攻击
role.WeaponTag =
"IronSword"
;
role.Attack(monster2);
role.Attack(monster3);
//魔剑攻击
role.WeaponTag =
"MagicSword"
;
role.Attack(monster3);
role.Attack(monster4);
role.Attack(monster4);
role.Attack(monster4);
role.Attack(monster4);
role.Attack(monster4);
Console.ReadLine();
}
}
}
|
程序运行结果以下:
图1.2 小李程序的运行结果
小李阐述完本身的想法并演示了Demo后,项目组长Peter首先确定了小李的思考能力、编程能力以及初步的面向对象分析与设计的思想,并认可小李的程序正确完成了需求中的功能。但同时,Peter也指出小李的设计存在一些问题,他请小于讲一下本身的见解。
小因而一名有五年软件架构经验的架构师,对软件架构、设计模式和面向对象思想有较深刻的认识。他向Peter点了点头,发表了本身的见解:
“小李的思考能力是不错的,有着基本的面向对象分析设计能力,而且程序正确完成了所须要的功能。不过,这里我想从架构角度,简要说一下我认为这个设计中存在的问题。
首先,小李设计的Role类的Attack方法很长,而且方法中有一个冗长的if…else结构,且每一个分支的代码的业务逻辑很类似,只是不多的地方不一样。
再者,我认为这个设计比较大的一个问题是,违反了OCP原则。在这个设计中,若是之后咱们增长一个新的武器,如倚天剑,每次攻击损失500HP,那么,咱们就要打开Role,修改Attack方法。而咱们的代码应该是对修改关闭的,当有新武器加入的时候,应该使用扩展完成,避免修改已有代码。
通常来讲,当一个方法里面出现冗长的if…else或switch…case结构,且每一个分支代码业务类似时,每每预示这里应该引入多态性来解决问题。而这里,若是把不一样武器攻击当作一个策略,那么引入策略模式(Strategy Pattern)是明智的选择。
最后说一个小的问题,被攻击后,减HP、死亡判断等都是怪物的职责,这里放在Role中有些不当。”
Tip:OCP原则,即开放关闭原则,指设计应该对扩展开放,对修改关闭。
Tip:策略模式,英文名Strategy Pattern,指定义算法族,分别封装起来,让他们之间能够相互替换,此模式使得算法的变化独立于客户。
小于边说,边画了一幅UML类图,用于直观表示他的思想。
图1.3 小于的设计
Peter让小李按照小于的设计重构Demo,小李看了看小于的设计图,很快完成。相关代码以下:
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
namespace
IGameLiAdv
{
internal
interface
IAttackStrategy
{
void
AttackTarget(Monster monster);
}
}
|
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
namespace
IGameLiAdv
{
internal
sealed
class
WoodSword : IAttackStrategy
{
public
void
AttackTarget(Monster monster)
{
monster.Notify(20);
}
}
}
|
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
namespace
IGameLiAdv
{
internal
sealed
class
IronSword : IAttackStrategy
{
public
void
AttackTarget(Monster monster)
{
monster.Notify(50);
}
}
}
|
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
namespace
IGameLiAdv
{
internal
sealed
class
MagicSword : IAttackStrategy
{
private
Random _random =
new
Random();
public
void
AttackTarget(Monster monster)
{
Int32 loss = (_random.NextDouble() < 0.5) ? 100 : 200;
if
(200 == loss)
{
Console.WriteLine(
"出现暴击!!!"
);
}
monster.Notify(loss);
}
}
}
|
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
namespace
IGameLiAdv
{
/// <summary>
/// 怪物
/// </summary>
internal
sealed
class
Monster
{
/// <summary>
/// 怪物的名字
/// </summary>
public
String Name {
get
;
set
; }
/// <summary>
/// 怪物的生命值
/// </summary>
private
Int32 HP {
get
;
set
; }
public
Monster(String name,Int32 hp)
{
this
.Name = name;
this
.HP = hp;
}
/// <summary>
/// 怪物被攻击时,被调用的方法,用来处理被攻击后的状态更改
/// </summary>
/// <param name="loss">这次攻击损失的HP</param>
public
void
Notify(Int32 loss)
{
if
(
this
.HP <= 0)
{
Console.WriteLine(
"此怪物已死"
);
return
;
}
this
.HP -= loss;
if
(
this
.HP <= 0)
{
Console.WriteLine(
"怪物"
+
this
.Name +
"被打死"
);
}
else
{
Console.WriteLine(
"怪物"
+
this
.Name +
"损失"
+ loss +
"HP"
);
}
}
}
}
|
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
namespace
IGameLiAdv
{
/// <summary>
/// 角色
/// </summary>
internal
sealed
class
Role
{
/// <summary>
/// 表示角色目前所持武器
/// </summary>
public
IAttackStrategy Weapon {
get
;
set
; }
/// <summary>
/// 攻击怪物
/// </summary>
/// <param name="monster">被攻击的怪物</param>
public
void
Attack(Monster monster)
{
this
.Weapon.AttackTarget(monster);
}
}
}
|
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
namespace
IGameLiAdv
{
class
Program
{
static
void
Main(
string
[] args)
{
//生成怪物
Monster monster1 =
new
Monster(
"小怪A"
, 50);
Monster monster2 =
new
Monster(
"小怪B"
, 50);
Monster monster3 =
new
Monster(
"关主"
, 200);
Monster monster4 =
new
Monster(
"最终Boss"
, 1000);
//生成角色
Role role =
new
Role();
//木剑攻击
role.Weapon =
new
WoodSword();
role.Attack(monster1);
//铁剑攻击
role.Weapon =
new
IronSword();
role.Attack(monster2);
role.Attack(monster3);
//魔剑攻击
role.Weapon =
new
MagicSword();
role.Attack(monster3);
role.Attack(monster4);
role.Attack(monster4);
role.Attack(monster4);
role.Attack(monster4);
role.Attack(monster4);
Console.ReadLine();
}
}
}
|
编译运行以上代码,获得的运行结果与上一版本代码基本一致。
Peter显然对改进后的代码比较满意,他让小李对照两份设计和代码,进行一个小结。小李简略思考了一下,并结合小于对一次设计指出的不足,说道:
“我认为,改进后的代码有以下优势:
第一,虽然类的数量增长了,可是每一个类中方法的代码都很是短,没有了之前Attack方法那种很长的方法,也没有了冗长的if…else,代码结构变得很清晰。
第二,类的职责更明确了。在第一个设计中,Role不但负责攻击,还负责给怪物减小HP和判断怪物是否已死。这明显不该该是Role的职责,改进后的代码将这两个职责移入Monster内,使得职责明确,提升了类的内聚性。
第三,引入Strategy模式后,不但消除了重复性代码,更重要的是,使得设计符合了OCP。若是之后要加一个新武器,只要新建一个类,实现IAttackStrategy接口,当角色须要装备这个新武器时,客户代码只要实例化一个新武器类,并赋给Role的Weapon成员就能够了,已有的Role和Monster代码都不用改动。这样就实现了对扩展开发,对修改关闭。”
Peter和小于听后都很满意,认为小李总结的很是出色。
IGame公司的讨论会还在进行着,内容是很是精彩,不过咱们先听到这里,由于,接下来,咱们要对其中某些问题进行一点探讨。别忘了,本文的主题但是依赖注入,这个主角还没登场呢!让主角等过久可很差。
咱们如今静下心来,再回味一下刚才的故事。由于,这个故事里面隐藏着依赖注入的出现缘由。我说过不仅一次,想真正认清一个事物,不能只看“它是什么?什么样子?”,而应该先弄清楚“它是怎么来的?是什么样的需求和背景促使了它的诞生?它被创造出来是作什么用的?”。
回想上面的故事。刚开始,主要需求是一个打怪的功能。小李作了一个初步面向对象的设计:抽取领域场景中的实体(怪物、角色等),封装成类,并为各个类赋予属性与方法,最后经过类的交互完成打怪功能,这应该算是面向对象设计的初级阶段。
在小李的设计基础上,架构师小于指出了几点不足,如不符合OCP,职责划分不明确等等,并根据状况引入策略模式。这是更高层次的面向对象设计。其实就核心来讲,小于只作了一件事:利用多态性,隔离变化。它清楚认识到,这个打怪功能中,有些业务逻辑是不变的,如角色攻击怪物,怪物减小HP,减到0怪物就会死;而变化的仅仅是不一样的角色持有不一样武器时,每次攻击的效用不同。因而他的架构,本质就是把变化的部分和不变的部分隔离开,使得变化部分发生变化时,不变部分不受影响。
咱们再仔细看看小于的设计图,这样设计后,有个基本的问题须要解决:如今Role不依赖具体武器,而仅仅依赖一个IAttackStrategy接口,接口是不能实例化的,虽然Role的Weapon成员类型定义为IAttackStrategy,但最终仍是会被赋予一个实现了IAttackStrategy接口的具体武器,而且随着程序进展,一个角色会装备不一样的武器,从而产生不一样的效用。赋予武器的职责,在Demo中是放在了测试代码里。
这里,测试代码实例化一个具体的武器,并赋给Role的Weapon成员的过程,就是依赖注入!这里要清楚,依赖注入实际上是一个过程的称谓!
下面,用稍微正式一点的语言,定义依赖注入产生的背景原因和依赖注入的含义。在读的过程当中,读者能够结合上面的例子进行理解。
依赖注入产生的背景:
随着面向对象分析与设计的发展,一个良好的设计,核心原则之一就是将变化隔离,使得变化部分发生变化时,不变部分不受影响(这也是OCP的目的)。为了作到这一点,要利用面向对象中的多态性,使用多态性后,客户类再也不直接依赖服务类,而是依赖于一个抽象的接口,这样,客户类就不能在内部直接实例化具体的服务类。可是,客户类在运做中又客观须要具体的服务类提供服务,由于接口是不能实例化去提供服务的。就产生了“客户类不许实例化具体服务类”和“客户类须要具体服务类”这样一对矛盾。为了解决这个矛盾,开发人员提出了一种模式:客户类(如上例中的Role)定义一个注入点(Public成员Weapon),用于服务类(实现IAttackStrategy的具体类,如WoodSword、IronSword和MagicSword,也包括之后加进来的全部实现IAttackStrategy的新类)的注入,而客户类的客户类(Program,即测试代码)负责根据状况,实例化服务类,注入到客户类中,从而解决了这个矛盾。
依赖注入的正式定义:
依赖注入(Dependency Injection),是这样一个过程:因为某客户类只依赖于服务类的一个接口,而不依赖于具体服务类,因此客户类只定义一个注入点。在程序运行过程当中,客户类不直接实例化具体服务类实例,而是客户类的运行上下文环境或专门组件负责实例化服务类,而后将其注入到客户类中,保证客户类的正常运行。
上面咱们从需求背景的角度,讲述了依赖注入的来源和定义。可是,若是依赖注入仅仅就只有这么点东西,那也没有什么值得讨论的了。可是,上面讨论的仅仅是依赖注入的内涵,其外延仍是很是普遍的,从依赖注入衍生出了不少相关的概念与技术,下面咱们讨论一下依赖注入的“那些事儿”。
依赖注入有不少种方法,上面看到的例子中,只是其中的一种,下面分别讨论不一样的依赖注入类型。
第一种依赖注入的方式,就是Setter注入,上面的例子中,将武器注入Role就是Setter注入。正式点说:
Setter注入(Setter Injection)是指在客户类中,设置一个服务类接口类型的数据成员,并设置一个Set方法做为注入点,这个Set方法接受一个具体的服务类实例为参数,并将它赋给服务类接口类型的数据成员。
图3.1 Setter注入示意
上图展现了Setter注入的结构示意图,客户类ClientClass设置IServiceClass类型成员_serviceImpl,并设置Set_ServiceImpl方法做为注入点。Context会负责实例化一个具体的ServiceClass,而后注入到ClientClass里。
下面给出Setter注入的示例代码。
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
namespace
SetterInjection
{
internal
interface
IServiceClass
{
String ServiceInfo();
}
}
|
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
namespace
SetterInjection
{
internal
class
ServiceClassA : IServiceClass
{
public
String ServiceInfo()
{
return
"我是ServceClassA"
;
}
}
}
|
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
namespace
SetterInjection
{
internal
class
ServiceClassB : IServiceClass
{
public
String ServiceInfo()
{
return
"我是ServceClassB"
;
}
}
}
|
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
namespace
SetterInjection
{
internal
class
ClientClass
{
private
IServiceClass _serviceImpl;
public
void
Set_ServiceImpl(IServiceClass serviceImpl)
{
this
._serviceImpl = serviceImpl;
}
public
void
ShowInfo()
{
Console.WriteLine(_serviceImpl.ServiceInfo());
}
}
}
|
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
namespace
SetterInjection
{
class
Program
{
static
void
Main(
string
[] args)
{
IServiceClass serviceA =
new
ServiceClassA();
IServiceClass serviceB =
new
ServiceClassB();
ClientClass client =
new
ClientClass();
client.Set_ServiceImpl(serviceA);
client.ShowInfo();
client.Set_ServiceImpl(serviceB);
client.ShowInfo();
}
}
}
|
运行结果以下:
图3.2 Setter注入运行结果
另一种依赖注入方式,是经过客户类的构造函数,向客户类注入服务类实例。
构造注入(Constructor Injection)是指在客户类中,设置一个服务类接口类型的数据成员,并以构造函数为注入点,这个构造函数接受一个具体的服务类实例为参数,并将它赋给服务类接口类型的数据成员。
图3.3 构造注入示意
图3.3是构造注入的示意图,能够看出,与Setter注入很相似,只是注入点由Setter方法变成了构造方法。这里要注意,因为构造注入只能在实例化客户类时注入一次,因此一点注入,程序运行期间是无法改变一个客户类对象内的服务类实例的。
因为构造注入和Setter注入的IServiceClass,ServiceClassA和ServiceClassB是同样的,因此这里给出另外ClientClass类的示例代码。
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
namespace
ConstructorInjection
{
internal
class
ClientClass
{
private
IServiceClass _serviceImpl;
public
ClientClass(IServiceClass serviceImpl)
{
this
._serviceImpl = serviceImpl;
}
public
void
ShowInfo()
{
Console.WriteLine(_serviceImpl.ServiceInfo());
}
}
}
|
能够看到,惟一的变化就是构造函数取代了Set_ServiceImpl方法,成为了注入点。
上面提到的注入方式,都是客户类被动接受所依赖的服务类,这也符合“注入”这个词。不过还有一种方法,能够和依赖注入达到相同的目的,就是依赖获取。
依赖获取(Dependency Locate)是指在系统中提供一个获取点,客户类仍然依赖服务类的接口。当客户类须要服务类时,从获取点主动取得指定的服务类,具体的服务类类型由获取点的配置决定。
能够看到,这种方法变被动为主动,使得客户类在须要时主动获取服务类,而将多态性的实现封装到获取点里面。获取点能够有不少种实现,也许最容易想到的就是创建一个Simple Factory做为获取点,客户类传入一个指定字符串,以获取相应服务类实例。若是所依赖的服务类是一系列类,那么依赖获取通常利用Abstract Factory模式构建获取点,而后,将服务类多态性转移到工厂的多态性上,而工厂的类型依赖一个外部配置,如XML文件。
不过,不论使用Simple Factory仍是Abstract Factory,都避免不了判断服务类类型或工厂类型,这样系统中总要有一个地方存在不符合OCP的if…else或switch…case结构,这种缺陷是Simple Factory和Abstract Factory以及依赖获取自己没法消除的,而在某些支持反射的语言中(如C#),经过将反射机制的引入完全解决了这个问题(后面讨论)。
下面给一个具体的例子,如今咱们假设有个程序,既可使用Windows风格外观,又可使用Mac风格外观,而内部业务是同样的。
图3.4 依赖获取示意
上图乍看有点复杂,不过若是读者熟悉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风格,编译运行上述代码,运行结果以下:
图3.5 配置Mac风格后的运行结果
如今,咱们不动程序,仅仅将配置文件中的“Mac”改成Windows,运行后结果以下:
图3.6 配置为Windows风格后的运行结果
从运行结果看出,咱们仅仅经过修改配置文件,就改变了整个程序的行为(咱们甚至没有从新编译程序),这就是多态性的威力,也是依赖注入效果。
本节共讨论了三种基本的依赖注入类别,有关更多依赖注入类别和不一样类别对比的知识,能够参考Martin Fowler的《Inversion of Control Containers and the Dependency Injection pattern》。
回想上面Dependency Locate的例子,咱们虽然使用了多态性和Abstract Factory,但对OCP贯彻的不够完全。在理解这点前,朋友们必定要注意潜在扩展在哪里,潜在会出现扩展的地方是“新的组件系列”而不是“组件种类”,也就是说,这里咱们假设组件就三种,不会增长新的组件,但可能出现新的外观系列,如须要加一套Ubuntu风格的组件,咱们能够新增UbuntuWindow、UbuntuButton、UbuntuTextBox和UbuntuFactory,并分别实现相应接口,这是符合OCP的,由于这是扩展。但咱们除了修改配置文件,还要无可避免的修改FactoryContainer,须要加一个分支条件,这个地方破坏了OCP。依赖注入自己是没有能力解决这个问题的,但若是语言支持反射机制(Reflection),则这个问题就迎刃而解。
咱们想一想,如今的难点是出在这里:对象最终仍是要经过“new”来实例化,而“new”只能实例化当前已有的类,若是将来有新类添加进来,必须修改代码。若是,咱们能有一种方法,不是经过“new”,而是经过类的名字来实例化对象,那么咱们只要将类的名字做为配置项,就能够实如今不修改代码的状况下,加载将来才出现的类。因此,反射给了语言“预见将来”的能力,使得多态性和依赖注入的威力大增。
下面是引入反射机制后,对上面例子的改进:
图3.7 引入反射机制的Dependency Locate
能够看出,引入反射机制后,结构简单了不少,一个反射工厂代替了之前的一堆工厂,Factory Container也不须要了。并且之后有新组件系列加入时,反射工厂是不用改变的,只需改变配置文件就能够完成。下面给出反射工厂和配置文件的代码。
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
using
System.Reflection;
using
System.Xml;
namespace
DependencyLocate
{
internal
static
class
ReflectionFactory
{
private
static
String _windowType;
private
static
String _buttonType;
private
static
String _textBoxType;
static
ReflectionFactory()
{
XmlDocument xmlDoc =
new
XmlDocument();
xmlDoc.Load(
"http://www.cnblogs.com/Config.xml"
);
XmlNode xmlNode = xmlDoc.ChildNodes[1].ChildNodes[0];
_windowType = xmlNode.ChildNodes[0].Value;
_buttonType = xmlNode.ChildNodes[1].Value;
_textBoxType = xmlNode.ChildNodes[2].Value;
}
public
static
IWindow MakeWindow()
{
return
Assembly.Load(
"DependencyLocate"
).CreateInstance(
"DependencyLocate."
+ _windowType)
as
IWindow;
}
public
static
IButton MakeButton()
{
return
Assembly.Load(
"DependencyLocate"
).CreateInstance(
"DependencyLocate."
+ _buttonType)
as
IButton;
}
public
static
ITextBox MakeTextBox()
{
return
Assembly.Load(
"DependencyLocate"
).CreateInstance(
"DependencyLocate."
+ _textBoxType)
as
ITextBox;
}
}
}
|
配置文件以下:
<?
xml
version="1.0" encoding="utf-8" ?>
<
config
>
<
window
>MacWindow</
window
>
<
button
>MacButton</
button
>
<
textBox
>MacTextBox</
textBox
>
</
config
>
|
反射不只能够与Dependency Locate结合,也能够与Setter Injection与Construtor Injection结合。反射机制的引入,下降了依赖注入结构的复杂度,使得依赖注入完全符合OCP,并为通用依赖注入框架(如Spring.NET中的IoC部分、Unity等)的设计提供了可能性。
这一节咱们讨论多态的活性及其与依赖注入类型选择间密切的关系。
首先说明,“多态的活性”这个术语是我我的定义的,由于我没有找到既有的概念名词能够表达个人意思,因此就本身造了一个词。这里,某多态的活性是指被此多态隔离的变化所发生变化的频繁程度,频繁程度越高,则活性越强,反之亦然。
上文说过,多态性能够隔离变化,可是,不一样的变化,发生的频率是不同的,这就使得多态的活性有所差异,这种差异影响了依赖注入的类型选择。
举例来讲,本文最开始提到的武器多态性,其活性很是高,由于在那个程序中,Role在一次运行中可能更换屡次武器。而如今咱们假设Role也实现了多态性,这是极可能的,由于在游戏中,不一样类型的角色(如暗夜精 灵、牛头人、矮人等)不少属性和业务是想通的,因此极可能经过一个IRole或AbstractRole抽象类实现多态性,不过,Role在实例化后(通常在用户登陆成功后),是不会变化的,不多有游戏容许同一个玩家在运行中变换Role类型,因此Role应该是一但实例化,就不会变化,但若是再实例化一个(如另外一个玩家登陆),则可能就变化了。最后,还有一种多态性是活性很是低的,如咱们熟悉的数据访问层多态性,即便咱们实现了SQL Server、Oracle和Access等多种数据库的访问层,并实现了依赖注入,但几乎遇不到程序运行着就改数据库或短时间内数据库频繁变更的状况。
以上不一样的多态性,不但特征不一样,其目的通常也不一样,总结以下:
高活多态性——指在客户类实例运行期间,服务类可能会改变的多态性。
中活多态性——指在客户类实例化后,服务类不会改变,但同一时间内存在的不一样实例可能拥有不一样类型的服务类。
低活多态性——指在客户类实例化后,服务类不会改变,且同一时间内全部客户类都拥有相同类型的服务类。
以上三种多态性,比较好的例子就是上文提到的武器多态性(高活)、角色多态性(中活)和数据访问层多态性(低活)。另外,咱们说一种多态性是空间稳定的,若是同一客户类在同一时间内的全部实例都依赖相同类型的服务类,反之则叫作空间不稳定多态性。咱们说一种多态性是时间稳定的,若是一个客户类在实例化后,因此来的服务类不能再次更改,反之则叫作时间不稳定多态性。显然,高活多态性时间和空间均不稳定;中活多态性是时间稳定的,但空间不稳定;低活多态性时间空间均稳定。
通常来讲,高活多态性适合使用Setter注入。由于Setter注入最灵活,也是惟一容许在同一客户类实例运行期间更改服务类的注入方式。而且这种注入通常由上下文环境经过Setter的参数指定服务类类型,方便灵活,适合频繁变化的高活多态性。
对于中活多态性,则适合使用Constructor注入。由于Constructor注入也是由上下文环境经过Construtor的参数指定服务类类型,但一点客户类实例化后,就不能进行再次注入,保证了其时间稳定性。
而对于低活多态性,则适合使用Dependency Locate并配合文件配置进行依赖注入,或Setter、Constructor配合配置文件注入,由于依赖源来自文件,若是要更改服务类,则须要更改配置文件,一则确保了低活多态性的时间和空间稳定性,二是更改配置文件的方式方便于大规模服务类替换。(由于低活多态性一旦改变行为,每每规模很大,如替换整个数据访问层,若是使用Setter和Construtor传参,程序中须要改变的地方不可胜数)
本质上,这种选择是由于不一样的依赖注入类型有着不一样的稳定性,你们能够细细体会“活性”、“稳定性”和“依赖注入类型”之间密切的关系。
上面讨论了诸多依赖注入的话题。说道依赖注入,就不能不说IoC Container(IoC容器),那么到底什么是IoC容器?咱们仍是先来看看它的出现背景。
咱们知道,软件开发领域有句著名的论断:不要重复发明轮子!由于软件开发讲求复用,因此,对于应用频繁的需求,老是有人设计各类通用框架和类库以减轻人们的开发负担。例如,数据持久化是很是频繁的需求,因而各类ORM框架应运而生;再如,对MVC的需求催生了Struts等一批用来实现MVC的框架。
随着面向对象分析与设计的发展和成熟,OOA&D被愈来愈普遍应用于各类项目中,然而,咱们知道,用OO就不可能不用多态性,用多态性就不可能不用依赖注入,因此,依赖注入变成了很是频繁的需求,而若是所有手工完成,不但负担过重,并且还容易出错。再加上反射机制的发明,因而,天然有人开始设计开发各类用于依赖注入的专用框架。这些专门用于实现依赖注入功能的组件或框架,就是IoC Container。
从这点看,IoC Container的出现有其历史必然性。目前,最著名的IoC也许就是Java平台上的Spring框架的IoC组件,而.NET平台上也有Spring.NET和Unity等。
前面曾经讨论了三种依赖注入方式,可是,想经过方式对IoC Container进行分类很困难,由于如今IoC Container都设计很完善,几乎支持全部依赖注入方式。不过,根据不一样框架的特性和惯用法,仍是能够讲IoC Container分为两个大类。
所谓重量级IoC Container,是指通常用外部配置文件(通常是XML)做为依赖源,并托管整个系统各个类的实例化的IoC Container。这种IoC Container,通常是承接了整个系统几乎全部多态性的依赖注入工做,并承接了全部服务类的实例化工做,并且这些实例化依赖于一个外部配置文件,这种IoC Container,很像经过一个文件,定义整个系统多态结构,视野宏大,想要很好驾驭这种IoC Container,须要必定的架构设计能力和丰富的实践经验。
Spring和Spring.NET是重量级IoC Container的例子。通常来讲,这种IoC Container稳定性有余而活性不足,适合进行低活多态性的依赖注入。
还有一种IoC Container,通常不依赖外部配置文件,而主要使用传参的Setter或Construtor注入,这种IoC Container叫作轻量级IoC Container。这种框架很灵活,使用方便,但每每不稳定,并且依赖点都是程序中的字符串参数,因此,不适合须要大规模替换和相对稳定的低活多态性,而对于高活多态性,有很好的效果。
Unity是一个典型的轻量级IoC Container。
Spring.NET是Java平台上Spring对.NET平台的移植,使用方法和Spring很像,而且功能强大,是.NET平台上大中型开发IoC Container的首选之一。除了DI外,Spring.NET也包括AOP等诸多功能。
Spring.NET的官方网站是:http://www.springframework.net/
对于小型项目和讲求敏捷的团队,Spring.NET可能有点过重量级,那么能够选择轻量级的Unity。Unity是微软patterns & practices团队推出的轻量级框架,很是好用,目前最新版本是1.2。
Unity的官方网站是:http://unity.codeplex.com/
参考文献
[1] Shivprasad koirala, Design pattern – Inversion of control and Dependency injection,http://www.codeproject.com/KB/aspnet/IOCDI.aspx
[2] Martin Fowler, Inversion of Control Containers and the Dependency Injection pattern,http://www.martinfowler.com/articles/injection.html
[3] Paul, IoC Types, http://docs.codehaus.org/display/PICO/IoC+Types
[4] Eric Freeman, Elisabeth Freeman. Head First Design Patterns. O’Reilly Media, 2004. ISBN 0596007142
[5] Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1995. ISBN 0201633612
[6] Patrick Smacchia 著,施凡等 译,C#和.NET2.0 平台、语言与框架。2008.1,人民邮电出版
[7] Jeffrey Rechter 著,CLR via C#(影印版)。2008.8,人民邮电出版