【架構篇】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。
  • 能夠轉載該博客,但必須著名博客來源。
相關文章
相關標籤/搜索