【架构篇】OCP和依赖注入

描述html

 本篇文章主要讲解 :编程

(1)OO设计OCP原则;segmentfault

(2)依赖注入引入设计模式

(3)依赖注入分析架构

(4)依赖注入种类app

1   内容区框架

1.1   IOC背景dom

(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()

 1 public abstract class People
 2     {
 3         private string Head;//
 4         private string Mouse;//
 5 
 6         public void Eat() //吃饭
 7         {
 8             //......
 9         }
10         public void Sleep() //睡觉
11         {
12             //......
13         }
14 
15         public abstract void WalkPosture(); //每一个人的走路姿式不同
16 17
18     }

UML类图以下:

 

(1)咱们向People类中添加Speak()方法,使其可以说汉语(普通话),则People类变为以下:

 1  public class People
 2     {
 3         private string Head;//
 4         private string Mouse;//
 5 
 6         public void Eat() //吃饭
 7         {
 8             //......
 9         }
10         public void Sleep() //睡觉
11         {
12             //......
13         }
14 
15         public abstract void WalkPosture();//每一个人的走路姿式不同
16  
17 18 public string SpeakLanguage() //说话 19 { 20 //普通话 21 } 22 23 }

此时,UML类图变为以下:

(2)具体的某我的,继承People类便可。

1 public class XiaoMing : People
2     {
3         //......
4     }

UML图以下:

2.2  对People类分析

People类UML图以下:

分析:

假设这样一个情景:即People类中不只仅是中国人,还有其余231个国家的人(每一个国家的语言并不彻底相同),咱们在本程序中,加入英国人,俄罗斯人,即People类中只有中国人,英国人,俄罗斯人三个国家的人。

作法一:

在People类中改写SpeakLanguage()方法。

 1 public class People
 2     {
 3         private string Head;//
 4         private string Mouse;//
 5 
 6         public void Eat() //吃饭
 7         {
 8             //......
 9         }
10         public void Sleep() //睡觉
11         {
12             //......
13         }
14 
15         public abstract WalkPosture();//每一个人的走路姿式不同
16 17 
18         public Language SpeakLanguage( Language language) //说话
19         {
20             if (language=="Chinese")
21             {
22                 //普通话
23             }
24             if (language=="English")
25             {
26                 //English
27             }
28             else 31             {
29                //Russian 
30             }
31 
32         }
33 
34     }

咱们来分析一下作法一的

问题:

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类

1 public class Language
2 {
3    //To add Language business codes
4 }

People类

 1     public class People
 2     {
 3         private string Head;//
 4         private string Mouse;//
 5 
 6         public void Eat() //吃饭
 7         {
 8             //......
 9         }
10         public void Sleep() //睡觉
11         {
12             //......
13         }
14 
15         public abstract WalkPosture();//每一个人的走路姿式不同
16 17 }

接口 ILanguage

1 public interface ILanguage
2     {
3         Language SpeakLanguage(Language language);
4     }

中国人

1 public class Chinese : People,ILanguage
2     {
3         // 继承People
4         // WalkPostrue
5         // 实现接口方法 Language SpeakLanguage(Language language);
6     }

 英国人

1 public class English : People,ILanguage
2     {
3          // 继承People
4         // WalkPostrue
5         // 实现接口方法 Language SpeakLanguage(Language language);
6     }

俄罗斯人

1 public class Chinese : People,ILanguage
2     {
3          // 继承People
4         // WalkPostrue
5         // 实现接口方法 Language SpeakLanguage(Language language);
6     }

  UML关系图以下:

若是你能将本OCP例子改成依赖注入,那么你没必要往下看了,由于你已经会了。

3   依赖注入

 在对依赖注入简要概述和对OCP简要分析以后,咱们来研究依赖注入。

3.1   例子:(引用)

一个叫IGame的游戏公司,正在开发一款ARPG游戏(动做&角色扮演类游戏,如魔兽世界、梦幻西游这一类的游戏)。通常这类游戏都有一个基本的功能,就是打怪(玩家攻击怪物,借此得到经验、虚拟货币和虚拟装备),而且根据玩家角色所装备的武器不一样,攻击效果也不一样.打怪功能中的某一个功能:

(1)、角色可向怪物实施攻击,一次攻击后,怪物掉部分HP,HP掉完后,怪物死亡。

(2)、角色可装配不一样武器,有木剑、铁剑、魔剑。

(3)、木剑每次攻击,怪物掉20PH,铁剑掉50HP,魔剑掉100PH。

IAttackStrategy接口

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5   
 6 namespace IGameLiAdv
 7 {
 8     internal interface IAttackStrategy
 9     {
10         void AttackTarget(Monster monster);
11     }
12 }

 WoodSword类

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5   
 6 namespace IGameLiAdv
 7 {
 8     internal sealed class WoodSword : IAttackStrategy
 9     {
10         public void AttackTarget(Monster monster)
11         {
12             monster.Notify(20);
13         }
14     }
15 }

 IronSword类

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5   
 6 namespace IGameLiAdv
 7 {
 8     internal sealed class IronSword : IAttackStrategy
 9     {
10         public void AttackTarget(Monster monster)
11         {
12             monster.Notify(50);
13         }
14     }
15 }

 MagicSword类

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5   
 6 namespace IGameLiAdv
 7 {
 8     internal sealed class MagicSword : IAttackStrategy
 9     {
10         private Random _random = new Random();
11   
12         public void AttackTarget(Monster monster)
13         {
14             Int32 loss = (_random.NextDouble() < 0.5) ? 100 : 200;
15             if (200 == loss)
16             {
17                 Console.WriteLine("出现暴击!!!");
18             }
19             monster.Notify(loss);
20         }
21     }
22 }

 Monster类

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5   
 6 namespace IGameLiAdv
 7 {
 8     /// <summary>
 9     /// 怪物
10     /// </summary>
11     internal sealed class Monster
12     {
13         /// <summary>
14         /// 怪物的名字
15         /// </summary>
16         public String Name { get; set; }
17   
18         /// <summary>
19         /// 怪物的生命值
20         /// </summary>
21         private Int32 HP { get; set; }
22   
23         public Monster(String name,Int32 hp)
24         {
25             this.Name = name;
26             this.HP = hp;
27         }
28   
29         /// <summary>
30         /// 怪物被攻击时,被调用的方法,用来处理被攻击后的状态更改
31         /// </summary>
32         /// <param name="loss">这次攻击损失的HP</param>
33         public void Notify(Int32 loss)
34         {
35             if (this.HP <= 0)
36             {
37                 Console.WriteLine("此怪物已死");
38                 return;
39             }
40   
41             this.HP -= loss;
42             if (this.HP <= 0)
43             {
44                 Console.WriteLine("怪物" + this.Name + "被打死");
45             }
46             else
47             {
48                 Console.WriteLine("怪物" + this.Name + "损失" + loss + "HP");
49             }
50         }
51     }
52 }

 Role类

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5   
 6 namespace IGameLiAdv
 7 {
 8     /// <summary>
 9     /// 角色
10     /// </summary>
11     internal sealed class Role
12     {
13         /// <summary>
14         /// 表示角色目前所持武器
15         /// </summary>
16         public IAttackStrategy Weapon { get; set; }
17   
18         /// <summary>
19         /// 攻击怪物
20         /// </summary>
21         /// <param name="monster">被攻击的怪物</param>
22         public void Attack(Monster monster)
23         {
24             this.Weapon.AttackTarget(monster);
25         }
26     }
27 }

 Program类

 1 namespace IGameLiAdv
 2 {
 3     class Program
 4     {
 5         static void Main(string[] args)
 6         {
 7             //生成怪物
 8             Monster monster1 = new Monster("小怪A", 50);
 9             Monster monster2 = new Monster("小怪B", 50);
10             Monster monster3 = new Monster("关主", 200);
11             Monster monster4 = new Monster("最终Boss", 1000);
12   
13             //生成角色
14             Role role = new Role();
15   
16             //木剑攻击
17             role.Weapon = new WoodSword();
18             role.Attack(monster1);
19   
20             //铁剑攻击
21             role.Weapon = new IronSword();
22             role.Attack(monster2);
23             role.Attack(monster3);
24   
25             //魔剑攻击
26             role.Weapon = new MagicSword();
27             role.Attack(monster3);
28             role.Attack(monster4);
29             role.Attack(monster4);
30             role.Attack(monster4);
31             role.Attack(monster4);
32             role.Attack(monster4);
33   
34             Console.ReadLine();
35         }
36     }
37 }

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。
  • 能够转载该博客,但必须著名博客来源。
相关文章
相关标签/搜索