【架构篇】OCP和依赖注入

【架构篇】OCP和依赖注入描述

本篇文章主要讲解 :

(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中写到:

One important characteristic of a framework is that the methods defined by the user to tailor the framework will often be called from within the framework itself, rather than from the user's application code.
The framework often plays the role of the main program in coordinating and sequencing application activity.
This inversion of control gives frameworks the power to serve as extensible skeletons. The methods supplied by the user tailor the generic algorithms defined in the framework for a particular application.
 
(2)GOF 《设计模式》
 
《设计模式》至少两次使用了控制反转,[1.6.7设计应支持变化]和[5.10模板方法模式]。
 
(3)Martin Fowler 文章Inversion of Control Containers and the Dependency Injection pattern
 
2004年,Martin Fowler 在其著名文章Inversion of Control Containers and the Dependency Injection pattern中,使用了该术语。但是,这些使用案例也使得IoC的含义变得含混。
 
1.2  IOC总结
 
(1) IOC(Inversion Of Control)是一种编程思想。但并非是面向对象编程的专用术语,是框架的主要特征之一,它主要包括依赖注入括依赖注入(Dependency Injection,简称DI)和依赖查找(Dependency Lookup)。
(2)在做软件设计和架构时,有两个重要原则是不能忽略的,即开闭原则(Open Close Principle,简称OCP)和高内聚,低耦合原则,然而IOC集合《设计模式》中的策略模式,一般能解决该问题(我们会在后面的文章中集合代码来分析)
(3)何为OCP?
很简单,一句话:“Closedfor Modification;Open for Extension",意思是,”对变更关闭;对扩展开放“。开闭原则的动机很简单:软件是变化的。一个软件实体应当对修改关闭,对扩展开放。也就是说,在设计一个模块的时候,应当对这个模块可以在不被修改的前提下被扩展。换言之,应当可以在不必修改源代码的情况下改变这个模块的行为,在保持系统一定稳定性的基础上,对系统进行扩展。这是面向对象设计(OOD)的基石,也是最重要的原则。OCP说明了软件设计应该尽可能地是架构稳定而又容易满足不同的需求。

《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类图如下:

【架构篇】OCP和依赖注入

(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类图变为如下:

【架构篇】OCP和依赖注入

(2)具体的某个人,继承People类即可。

 public class XiaoMing : People
{
//......
}

UML图如下:

【架构篇】OCP和依赖注入

2.2  对People类分析

People类UML图如下:

【架构篇】OCP和依赖注入

分析:

假设这样一个情景:即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和依赖注入

如果你能将本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关系图

【架构篇】OCP和依赖注入

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   依赖注入的种类

依赖注入大致可分为如下种类:

【架构篇】OCP和依赖注入

限于篇幅的限制,依赖注入种类分析,将在以后的文章中与大家分享。

5   参考文献

【01】https://segmentfault.com/a/1190000010456858

【02】Head First设计模式

6  版权区

  • 感谢您的阅读,若有不足之处,欢迎指教,共同学习、共同进步。
  • 博主网址:http://www.cnblogs.com/wangjiming/。
  • 极少部分文章利用读书、参考、引用、抄袭、复制和粘贴等多种方式整合而成的,大部分为原创。
  • 如您喜欢,麻烦推荐一下;如您有新想法,欢迎提出,邮箱:2016177728@qq.com。
  • 可以转载该博客,但必须著名博客来源。
上一篇:使用sshtunnel实现python公网连接阿里云mongo服务器


下一篇:php ftp文件上传函数--新手入门参考