描述
本篇文章主要讲解 :
(1)OO设计OCP原则;
(2)依赖注入引入
(3)依赖注入分析
(4)依赖注入种类
1 内容区
1.1 IOC背景
(1)Ralph E. Johnson & Brian Foote 论文 《Designing Reusable Classes》
早在1988年,Ralph E. Johnson & Brian Foote在论文Designing Reusable Classes中写到:
《OO面向对象设计七大原则,》,请参照我另外一篇文章 OO面向对象设计七大原则 .
2 OCP分析
OCP原则(Open Close Principle),核心思想是封闭修改(隔离变化),支持扩展(继承,目的是复用)。
为了分析清楚OCP,我们这里以人为研究对象,即把人当作超类。
2.1 定义超类(People类)
在定义一个类时,主要关心类的特性(Class 中的属性)和行为(Class 中的方法),这里,我们假设超类People中存在如下属性和方法:
a.属性:头,嘴
b.方法:Eat(),Sleep(),WalkPosture()
public abstract class People
{
private string Head;//头
private string Mouse;//嘴 public void Eat() //吃饭
{
//......
}
public void Sleep() //睡觉
{
//......
} public abstract void WalkPosture(); //每个人的走路姿势不一样 }
UML类图如下:
(1)我们向People类中添加Speak()方法,使其能够说汉语(普通话),则People类变为如下:
public class People
{
private string Head;//头
private string Mouse;//嘴 public void Eat() //吃饭
{
//......
}
public void Sleep() //睡觉
{
//......
} public abstract void WalkPosture();//每个人的走路姿势不一样
public string SpeakLanguage() //说话
{
//普通话
} }
此时,UML类图变为如下:
(2)具体的某个人,继承People类即可。
public class XiaoMing : People
{
//......
}
UML图如下:
2.2 对People类分析
People类UML图如下:
分析:
假设这样一个情景:即People类中不仅仅是中国人,还有其他231个国家的人(每个国家的语言并不完全相同),我们在本程序中,加入英国人,俄罗斯人,即People类中只有中国人,英国人,俄罗斯人三个国家的人。
做法一:
在People类中改写SpeakLanguage()方法。
public class People
{
private string Head;//头
private string Mouse;//嘴 public void Eat() //吃饭
{
//......
}
public void Sleep() //睡觉
{
//......
} public abstract WalkPosture();//每个人的走路姿势不一样 public Language SpeakLanguage( Language language) //说话
{
if (language=="Chinese")
{
//普通话
}
if (language=="English")
{
//English
}
else {
//Russian
} } }
我们来分析一下做法一的
问题:
Q1:由于直接修改超类People中的方法,违背了OO软件设计开闭原则(Open Close Principle,简称OCP);
Q2:如果再把其他国家加进来,那么SpeakLanguage() 方法体 会有很多 if.....else.....,不利于代码维护;
方法二:
根据OCP原则,对修改关闭,对扩展开放;在超类People中:
(1)属性Head,Mouse,每个人都具有;
(2)方法Eat(),Sleep(),每个人都具有;
(3)方法WalkPosture(),每个人走路的姿势不一样,可以用抽象方法来实现;
(4)方法SpeakLanguage(Language language),每个国籍的人,说话的语言不一定相同,这是类中变化的部分,需要独立开来;
因此,可以改写为如下:
定义一个Language类
Language类
public class Language
{
//To add Language business codes
}
People类
public class People
{
private string Head;//头
private string Mouse;//嘴 public void Eat() //吃饭
{
//......
}
public void Sleep() //睡觉
{
//......
} public abstract WalkPosture();//每个人的走路姿势不一样
}
接口 ILanguage
public interface ILanguage
{
Language SpeakLanguage(Language language);
}
中国人
public class Chinese : People,ILanguage
{
// 继承People
// WalkPostrue
// 实现接口方法 Language SpeakLanguage(Language language);
}
英国人
public class English : People,ILanguage
{
// 继承People
// WalkPostrue
// 实现接口方法 Language SpeakLanguage(Language language);
}
俄罗斯人
public class Chinese : People,ILanguage
{
// 继承People
// WalkPostrue
// 实现接口方法 Language SpeakLanguage(Language language);
}
UML关系图如下:
如果你能将本OCP例子改为依赖注入,那么你不必往下看了,因为你已经会了。
3 依赖注入
在对依赖注入简要概述和对OCP简要分析之后,我们来研究依赖注入。
3.1 例子:(引用)
一个叫IGame的游戏公司,正在开发一款ARPG游戏(动作&角色扮演类游戏,如魔兽世界、梦幻西游这一类的游戏)。一般这类游戏都有一个基本的功能,就是打怪(玩家攻击怪物,借此获得经验、虚拟货币和虚拟装备),并且根据玩家角色所装备的武器不同,攻击效果也不同.打怪功能中的某一个功能:
(1)、角色可向怪物实施攻击,一次攻击后,怪物掉部分HP,HP掉完后,怪物死亡。
(2)、角色可装配不同武器,有木剑、铁剑、魔剑。
(3)、木剑每次攻击,怪物掉20PH,铁剑掉50HP,魔剑掉100PH。
IAttackStrategy接口
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; namespace IGameLiAdv
{
internal interface IAttackStrategy
{
void AttackTarget(Monster monster);
}
}
WoodSword类
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();
}
}
}
IronSword类
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();
}
}
}
MagicSword类
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) ? : ;
if ( == loss)
{
Console.WriteLine("出现暴击!!!");
}
monster.Notify(loss);
}
}
}
Monster类
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 <= )
{
Console.WriteLine("此怪物已死");
return;
} this.HP -= loss;
if (this.HP <= )
{
Console.WriteLine("怪物" + this.Name + "被打死");
}
else
{
Console.WriteLine("怪物" + this.Name + "损失" + loss + "HP");
}
}
}
}
Role类
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);
}
}
}
Program类
namespace IGameLiAdv
{
class Program
{
static void Main(string[] args)
{
//生成怪物
Monster monster1 = new Monster("小怪A", );
Monster monster2 = new Monster("小怪B", );
Monster monster3 = new Monster("关主", );
Monster monster4 = new Monster("最终Boss", ); //生成角色
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();
}
}
}
UML关系图
3.2 分析:
引入Strategy模式后,不但消除了重复性代码,更重要的是,使得设计符合了OCP。如果以后要加一个新武器,只要新建一个类,实现IAttackStrategy接口,当角色需要装备这个新武器时,客户代码只要实例化一个新武器类,并赋给Role的Weapon成员就可以了,已有的Role和Monster代码都不用改动。这样就实现了对扩展开发,对修改关闭。
上面例子的第二种实现中,Role不依赖具体武器,而仅仅依赖一个IAttackStrategy接口,接口是不能实例化的,虽然Role的Weapon成员类型定义为IAttackStrategy,但最终还是会被赋予一个实现了IAttackStrategy接口的具体武器,并且随着程序进展,一个角色会装备不同的武器,从而产生不同的效用。赋予武器的职责,在Demo中是放在了测试代码里。
这里,测试代码实例化一个具体的武器,并赋给Role的Weapon成员的过程,就是依赖注入!这里要清楚,依赖注入其实是一个过程的称谓!
依赖注入产生的背景:
随着面向对象分析与设计的发展,一个良好的设计,核心原则之一就是将变化隔离,使得变化部分发生变化时,不变部分不受影响(这也是OCP的目的)。为了做到这一点,要利用面向对象中的多态性,使用多态性后,客户类不再直接依赖服务类,而是依赖于一个抽象的接口,这样,客户类就不能在内部直接实例化具体的服务类。但是,客户类在运作中又客观需要具体的服务类提供服务,因为接口是不能实例化去提供服务的。就产生了“客户类不准实例化具体服务类”和“客户类需要具体服务类”这样一对矛盾。为了解决这个矛盾,开发人员提出了一种模式:客户类(如上例中的Role)定义一个注入点(Public成员Weapon),用于服务类(实现IAttackStrategy的具体类,如WoodSword、IronSword和MagicSword,也包括以后加进来的所有实现IAttackStrategy的新类)的注入,而客户类的客户类(Program,即测试代码)负责根据情况,实例化服务类,注入到客户类中,从而解决了这个矛盾。
3.3 依赖注入的正式定义:
依赖注入(Dependency Injection),是这样一个过程:由于某客户类只依赖于服务类的一个接口,而不依赖于具体服务类,所以客户类只定义一个注入点。在程序运行过程中,客户类不直接实例化具体服务类实例,而是客户类的运行上下文环境或专门组件负责实例化服务类,然后将其注入到客户类中,保证客户类的正常运行。
3.4 依赖注入总结
(1)组成要素
a.接口及其实现(剥离变化)
b.客户类和服务类
(2)核心思想
a.延迟注入服务,并不是一开始就注入服务,即在用到时,才通过接口形式注入服务;
4 依赖注入的种类
依赖注入大致可分为如下种类:
限于篇幅的限制,依赖注入种类分析,将在以后的文章中与大家分享。
5 参考文献
【01】https://segmentfault.com/a/1190000010456858
【02】Head First设计模式
6 版权区
- 感谢您的阅读,若有不足之处,欢迎指教,共同学习、共同进步。
- 博主网址:http://www.cnblogs.com/wangjiming/。
- 极少部分文章利用读书、参考、引用、抄袭、复制和粘贴等多种方式整合而成的,大部分为原创。
- 如您喜欢,麻烦推荐一下;如您有新想法,欢迎提出,邮箱:2016177728@qq.com。
- 可以转载该博客,但必须著名博客来源。