您的位置:首页 > 其它

IOC入门 转:将文章简化理解。

2012-11-19 21:01 357 查看
依赖注入:

文章首先引入一个游戏公司的设计故事:



原始Demo设计:

// /// <summary>
// /// 怪物
// /// </summary>
//internal sealed class Monster
//{
// //怪物名称
// public string Name { get; set; }

// /// <summary>
// /// 怪物的生命值
// /// </summary>
// public Int32 HP { get; set; }

// public Monster(string name, Int32 hp)
// {
// this.Name = name;
// this.HP = hp;
// }
//}

// /// <summary>
// /// 角色
// /// </summary>
//internal sealed class Role
//{
// private Random _random = new Random();

// /// <summary>
// /// 表示角色目前所持有的武器的字符串
// /// </summary>
// public string WeaponTag { get; set; }

// //攻击怪物
// 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("角色手里没有武器,无法攻击!");
// }
// }
//}

对于这个程序的设计,有以下的不足:

1、 首先,Role类的Attack方法很长,并且方法中有一个冗长的if…else结构,且每个分支的代码的业务逻辑很相似,只是很少的地方不同。

2、 这里的设计违反了OCP【即开放关闭原则】原则。在这个设计中,如果我们增加一个新的武器,那么,我们就要打开Role,修改Attack方法。而我们的代码应该是对修改关闭的,当有新的武器加入的时候,应该使用扩展完成,避免修改已有的代码。【注:一般来说,当一个方法中出现冗长if….elseswitch….case结构,且每个分支代码业务相似时,往往预示这里应该引入多态性来解决问题。

3、 这里有一个小的问题,被攻击后,减HP、死亡判断等都是怪物的职责,这里放在Role中有些不当。

下面是根据上面的问题,重新设计的类图关系:



//武器的攻击接口
internal interface IAttackStrategy
{
void AttackTarget(Monster monster);
}

//木剑
internal sealed class WoodSword : IAttackStrategy
{

public void AttackTarget(Monster monster)
{
monster.Notify(20);
}
}

//铁剑
internal sealed class IronSword : IAttackStrategy
{
public void AttackTarget(Monster monster)
{
monster.Notify(50);
}
}

//魔剑
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);
}
}

/// <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;
}

/// <summary>
/// 怪物被攻击时,被调用的方法,用来处理被攻击后的状态更改
/// </summary>
/// <param name="loss"></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");
}
}
}

/// <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);
}
}

两者代码相比较,后一种有以下优点:

1、 虽然类的数量增加了,但是每个类中的方法的代码都非常短,没有了以前Attack方法的那种很长的方法,也没有了冗长的if….else,代码结构变得和清晰。

2、 类的职责更加明确了。

3、 设计符合了OCP。

以上是不同的讨论,到此为止。

2、探究依赖注入

2.1故事的启迪

我们现在静下心来,再回味一下刚才的故事。因为,这个故事里面隐藏着依赖注入的出现原因。我说过不只一次,想真正认清一个事物,不能只看它是什么?什么样子?,而应该先弄清楚它是怎么来的?是什么样的需求和背景促使了它的诞生?它被创造出来是做什么用的?

会想上面的故事,主要需求就是一个打怪的功能,第一部分的代码做了一个初步的面向对象的设计:抽取领取场景中的实体(怪物、角色等),封装成类,并为各个类赋予属性与方法,最后通过类的交互完成打怪功能,这应该算是面向对象设计的初级阶段。

第二部分代码,利用多态性,隔离变化。它清楚认识到,这个打怪功能中,有些业务逻辑是不变的,如角色攻击怪物,怪物减少HP,减到0怪物就会死;而变化的仅仅是不同的角色持有不同武器时,每次攻击的效用不一样。

我们再仔细看看第二部分的设计图,这样设计后,有个基本的问题需要解决:现在Role不依赖具体武器,而仅仅依赖一个IAttackStrategy接口,接口是不能实例化的,虽然Role的Weapon成员类型定义为IAttackStrategy,但最终还是会被赋予一个实现了IAttackStrategy接口的具体武器,并且随着程序进展,一个角色会装备不同的武器,从而产生不同的效用。

在这里,实例化一个具体的武器,并赋予RoleWeapon成员的这个过程,就是依赖注入。这里要注意,依赖注入其实就是一个过程的称谓。

2.2 正式定义依赖注入

依赖注入产生的背景:

随着面向对象分析与设计的发展,一个良好的设计,核心原则之一就是将变化隔离,使得变化部分发生变化时,不便部分不受影响。为了做到这一点,要利用面向对象中的多态性,使用多态性后,客户类不在直接依赖服务类,而是依赖一个抽象的接口,这样,客户类就不能在内部直接实例化具体的服务类。但是,客户类在运行中又客观需要具体的服务类提供服务,因为接口是不能实例化去提供服务的。这就产生了“客户类不准实例化具体服务类”和“客户类需要具体服务类”这样一对矛盾。为了解决这个矛盾,开发人员提出了一个模式:客户类(如上例的Role)定义了一个注入点(public成员Weapon),用语服务类(实现IattackStrategy的具体类,如WoodSword、IroSword和MagicSword,也包括以后加进来的所有实现IattackStrategy的新类)的注入。

依赖注入的正式定义:

依赖注入(Dependency Injection),是这样一个过程:由于某客户类只依赖于服务类的一个接口,而不依赖于具体服务类,所以客户类只定义一个注入点。在程序运行过程中,客户类不直接实例化具体服务类实例,而是客户类的运行上下文环境或专门组件负责实例化服务类,然后将其注入到客户类中,保证客户类的正常运行。

3、依赖注入那些事儿

上面的讨论仅仅是依赖注入的内涵,其外延还是非常广泛的。

3.1 依赖注入的类别

3.1.1 Setter注入

在第二个例子中,将武器注入Role就是Setter注入。正式点说:Setter注入(Setter Injection)是指在客户类中,设置一个服务类接口类型的数据成员,并设置一个Set方法作为注入点,这个Set方法接受一个具体的服务类实例为参数,并将它赋给服务类接口类型的数据成员。





上图展示了Setter注入的结构示意图。客户类ClientClass设置IServiceClass类型成员_serviceImpl,并设置Set_ServiceImpl方法作为注入点。Context会负责实例化一个具体的ServiceClass,然后注入到ClientClass里。

3.1.2 构造注入

另外一种依赖注入方式,就是通过客户类的构造函数,向客户类注入服务类实例。

构造注入(Constructor Injection)是指在客户类中,设置一个服务类接口类型的数据成员,并以构造函数为注入点,这个构造函数接受一个具体的服务类实例为参数,并将它赋给服务类接口类型的数据成员。

3.1.3 依赖获取

上面提到的注入方式,都是客户类被动接受所依赖的服务类,这也符合”注入”这个词。不过还有一种方法,可以和依赖注入达到相同的目的,就是依赖获取。

依赖获取(Dependency Locate)是指在系统中提供一个获取点,客户类仍然依赖服务类的接口。当客户类需要服务类时,从获取点主动去的指定的服务类,具体的服务类类型由获取点的配置决定。

下面使用一个具体的案例,现在我们假设有个程序,既可以使用Windows风格外观,又可以使用mac风格外观,而内部业务是一样的。



上图有点复杂,但是如果熟悉Abstract Factory模式,应该很容易看懂。这就是Abstract Factory模式在实际中的一个应用。这里的Factory Container作为获取点,是一个静态类,他的“Type构造函数”一句外部的XML配置文件,决定实例化哪个工厂。下面来看具体代码【这里只是Button组件的代码】。

//按钮接口
internal interface IButton
{
string ShowInfo();
}

//windows风格按钮
internal sealed class WindowsButton : IButton
{
public string Description { get; private set; }

public WindowsButton()
{
this.Description = "Windows风格按钮";
}
public string ShowInfo()
{
return this.Description;
}
}

//Mac风格按钮
internal sealed class MacButton : IButton
{

public string Description { get; private set; }

public MacButton()
{
this.Description = "Mac风格按钮";
}
public string ShowInfo()
{
return this.Description;
}
}

//工厂接口
internal interface IFactory
{
IButton MakeButton();
}

//windows组件工厂
internal sealed class WindowsFactory : IFactory
{

public IButton MakeButton()
{
return new WindowsButton();
}
}

//mac组件工厂
internal sealed class MacFactory : IFactory
{
public IButton MakeButton()
{
return new MacButton();
}
}

//获取点
internal static class FactoryContainer
{
public static IFactory factory { get; private set; }

static FactoryContainer()
{
//获取配置配置文件的节点,如:Mac
//factory = new WindowsFactory();

//调用
// IFactory factory = FactoryContainer.factory;
//IWindow window = factory.MakeWindow();
}
}

从测试运行结果中,我们可以看到,仅仅通过修改配置文件,就改变了整个程序的行为(我们甚至没有重新编译程序),这就是多态性的威力,也是依赖注入效果。

未完待续..... 转至:/article/5807119.html 这篇文章写得不错。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: