【架构篇】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中写到:
《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。
- 可以转载该博客,但必须著名博客来源。
【架构篇】OCP和依赖注入的更多相关文章
- WPF 高级篇 MVVM (MVVMlight) 依赖注入使用Messagebox
原文:WPF 高级篇 MVVM (MVVMlight) 依赖注入使用Messagebox MVVMlight 实现依赖注入 把弹框功能 和接口功能注入到各个插件中 使用依赖注入 先把所有的ViewMo ...
- .NET Core基础篇之:依赖注入DependencyInjection
依赖注入已经不是什么新鲜话题了,在.NET Framework时期就已经出现了各种依赖注入框架,比如:autofac.unity等.只是在.net core微软将它搬上了台面,不用再依赖第三方组件(那 ...
- java框架篇---spring IOC依赖注入
spring依赖注入的方式有4种 构造方法注入 属性注入 工厂注入 注解注入 下面通过一个实例统一讲解: User.java package com.bjsxt.model; public class ...
- C# 依赖注入
http://www.cnblogs.com/leoo2sk/archive/2009/06/17/1504693.html 这篇文章真的非常非常好···绝对值得收藏学习. 目录 目录 1 ...
- c#之依赖注入
C# 依赖注入 http://www.cnblogs.com/leoo2sk/archive/2009/06/17/1504693.html 1 IGame游戏公司的故事 1.1 讨论会 话说有一个叫 ...
- C#中的依赖注入那些事儿
目录 目录 1 IGame游戏公司的故事 1.1 讨论会 1.2 实习生小李的实现方法 1.3 架构师的建议 1.4 小李的小结 2 探究依赖注入 2.1 故事的启迪 2.2 正式定义依赖注入 3 依 ...
- C#基础知识之依赖注入
目录 1 IGame游戏公司的故事 1.1 讨论会 1.2 实习生小李的实现方法 1.3 架构师的建议 1.4 小李的小结 2 探究依赖注入 2.1 故事的启迪 2.2 正式定义依赖注入 3 依赖注入 ...
- Spring依赖注入的三种方式
看过几篇关于Spring依赖注入的文章,自己简单总结了一下,大概有三种方式: 1.自动装配 通过配置applicationContext.xml中的标签的default-autowire属性,或者标签 ...
- 依赖注入[2]: 基于IoC的设计模式
正如我们在<控制反转>提到过的,很多人将IoC理解为一种"面向对象的设计模式",实际上IoC自身不仅与面向对象没有必然的联系,它也算不上是一种设计模式.一般来讲,设计模 ...
随机推荐
- 使用 paddle来进行文本生成
paddle 简单介绍 paddle 是百度在2016年9月份开源的深度学习框架. 就我最近体验的感受来说的它具有几大优点: 1. 本身内嵌了许多和实际业务非常贴近的模型比如个性化推荐,情感分析,词向 ...
- eclipse导入android studio时一些异常的处理
Error:Execution failed for task ':app:compileDebugNdk'. > Error: Your project contains C++ files ...
- 初识Http协议抓包工具—Fiddler
1.Fiddler简介 Fiddler是用一款使用C#编写的http协议调试代理工具.它支持众多的http调试任务,能够记录并检查所有你的电脑和互联网之间的http通讯,可以设置断点,查看所有的“进出 ...
- Web性能测试工具之ab入门篇
1. ab简介 ab全称Apache Bench,是apache附带的一个小工具,它可以同时模拟多个并发请求,测试apache等Web服务器的最大负载压力. 本文通过一个简单的示例,介绍了使用ab进行 ...
- Python Web框架篇:Django文件上传
上传方式: - Form表单上传文件 - Ajax上传文件 - 基于form表单和iframe自己实现ajax请求 1,创建项目 2,settings配置(注册app01,static路径等等这些)及 ...
- 关于安卓手机的牛逼软件termux使用
最近在学着用linux,偶尔发现了一款神奇的软件--termux,termux是一款来自国外的终端模拟器,是运行在内部存储上的程序(不在内存卡上),功能比较强大,启动程序之后会进入命令行终端,需要基本 ...
- rpm命令常用选项
安装rpm包 # rpm -ivh ***.rpm #其中i表示安装,v表示显示安装过程,h表示显示进度 升级rpm包 # rpm -Uvh ***.rpm 删除软件包 # rpm -e PACKAG ...
- spring boot + thymeleaf 3 国际化
在给spring boot 1.5.6 + thymeleaf 3进行国际化时,踩了一个坑(其实不止一个). 现象: 看到了吧, 就是取值的key, 后面被加了_en_US 或 _zh_CN, 以及前 ...
- php缓存模块apc可能导致php-fpm终止
如果你的网站出现502错误.同时你网站中又使用了apc模块来做缓存处理.那么这篇文章兴许能帮到你. 首先,查看了php-fpm 的进程数. 发现php-fpm的进程数已经到达了php-fpm.conf ...
- 使用qt制作一个简单的计算器
前言:今天使用qt制作了一个很简单的计算器,觉得挺有意思的,所以在这里跟大家分享一下. 这里先跟大家说说使用到的函数: 一.槽连接函数 connect(信号发送者,发送的信号,信号接收者,信号接收者的 ...