【架构篇】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自身不仅与面向对象没有必然的联系,它也算不上是一种设计模式.一般来讲,设计模 ...
随机推荐
- GoldenGate 复制进程报错"OGG-01296 Error mapping",丢弃文件报错“Mapping problem with delete record (target format)”,且实际条目存在
故障描述: (1).复制进程 Abended,通过view report语句查看可发现类似如下的报错: 2017-10-23 15:01:43 ERROR OGG-01296 Error mappin ...
- Mapper 动态代理方式
Mapper接口开发方法只需要程序员编写Mapper接口(相当于Dao接口),由Mybatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法. Mapper接口开发 ...
- C++开发象棋一 绘制棋盘
这是我要和大家分享的基于C++和MFC开发的一个象棋程序,目的是练习编程实践和大家分享同时希望大家能给出指教. 进入主题 一.棋盘分析 这是我绘制的棋盘,棋盘的组成由9条竖线和10条横线构成.这儿我们 ...
- java笔记04: String的理解与运用
一,“==”与equals() 运行以下代码,如何解释其输出结果? public class StringPool { public static void main(String args[]) { ...
- PHP常用配置
Php配置文件:php.ini(使用‘;’表示注释) Php的配置项可以在配置文件中配置,也可以在脚本中使用ini_set()函数临时配置. 语言相关配置: 1. engine:设置PHP引擎是否可用 ...
- spring 学习笔记1
Spring 学习记录 任何一个成功的应用都是由多个为了实现某一个业务目标而相互协作的组件构成的.这些组件必须彼此了解,并相互协作来完成工作. 在Spring 中,对象无需自己负责查找或创建与其关联的 ...
- Mybatis报错:Parameter 'list' not found. Available parameters are [groupList, param1]
GroupDao.java 里面定义的方法: void batchInsertLog(@Param("groupList") List<MktPromotionIntegra ...
- spark三种连接Join
本文主要介绍spark join相关操作. 讲述spark连接相关的三个方法join,left-outer-join,right-outer-join,在这之前,我们用hiveSQL先跑出了结果以方便 ...
- django 实现同一个ip十分钟内只能注册一次(redis版本)
上一篇文章,django 实现同一个ip十分钟内只能注册一次 的时候,我们在注册的时候选择使用的使我们的数据库来报错我们的注册的ip信息,可是如果数据量大,用户多的时候,单单靠我们的数据库 来储存我们 ...
- window.onload,document.ready
执行时间 window.onload必须等到页面内包括图片的所有元素加载完毕后才能执行. $(document).ready()是DOM结构绘制完毕后就执行,不必等到加载完毕. 编写个数不同 wind ...