依賴注入那些事兒(上)[轉載]

目錄

寫在前面的話

目錄

1 IGame遊戲公司的故事

    1.1 討論會

    1.2 實習生小李的實現方法

    1.3 架構師的建議

    1.4 小李的小結

2 探究依賴注入

    2.1 故事的啓迪

    2.2 正式定義依賴注入

3 依賴注入那些事兒

    3.1 依賴注入的類別

        3.1.1 Setter注入

        3.1.2 Construtor注入

        3.1.3 依賴獲取

    3.2 反射與依賴注入

    3.3 多態的活性與依賴注入

        3.3.1 多態性的活性

        3.3.2 不同活性多態性依賴注入的選擇

4 IoC Container

    4.1 IoC Container出現的必然性

    4.2 IoC Container的分類

        4.2.1 重量級IoC Container

        4.2.2 輕量級IoC Container

    4.3 .NET平臺上典型IoC Container推介

        4.3.1 Spring.NET

        4.3.2 Unity

參考文獻

1 IGame遊戲公司的故事

1.1 討論會

話說有一個叫IGame的遊戲公司,正在開發一款ARPG遊戲(動作&角色扮演類遊戲,如魔獸世界、夢幻西遊這一類的遊戲)。一般這類遊戲都有一個基本的功能,就是打怪(玩家***怪物,藉此獲得經驗、虛擬貨幣和虛擬裝備),並且根據玩家角色所裝備的武器不同,***效果也不同。這天,IGame公司的開發小組正在開會對打怪功能中的某一個功能點如何實現進行討論,他們面前的大屏幕上是這樣一份需求描述的ppt:

圖1.1 需求描述ppt

各個開發人員,面對這份需求,展開了熱烈的討論,下面我們看看討論會上都發生了什麼。

1.2 實習生小李的實現方式

在經過一番討論後,項目組長Peter覺得有必要整理一下各方的意見,他首先詢問小李的看法。小李是某學校計算機系大三學生,對遊戲開發特別感興趣,目前是IGame公司的一名實習生。

經過短暫的思考,小李闡述了自己的意見:

“我認爲,這個需求可以這麼實現。HP當然是怪物的一個屬性成員,而武器是角色的一個屬性成員,類型可以使字符串,用於描述目前角色所裝備的武器。角色類有一個***方法,以被***怪物爲參數,當實施一次***時,***方法被調用,而這個方法首先判斷當前角色裝備了什麼武器,然後據此對被***怪物的HP進行操作,以產生不同效果。”

而在闡述完後,小李也飛快的在自己的電腦上寫了一個Demo,來演示他的想法,Demo代碼如下。

Code:怪物

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5:  
   6: namespace IGameLi
   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:         public Int32 HP { get; set; }
  22:  
  23:         public Monster(String name,Int32 hp)
  24:         {
  25:             this.Name = name;
  26:             this.HP = hp;
  27:         }
  28:     }
  29: }

Code:角色

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5:  
   6: namespace IGameLi
   7: {
   8:     /// <summary>
   9:     /// 角色
  10:     /// </summary>
  11:     internal sealed class Role
  12:     {
  13:         private Random _random = new Random();
  14:  
  15:         /// <summary>
  16:         /// 表示角色目前所持武器的字符串
  17:         /// </summary>
  18:         public String WeaponTag { get; set; }
  19:  
  20:         /// <summary>
  21:         /// ***怪物
  22:         /// </summary>
  23:         /// <param name="monster">被***的怪物</param>
  24:         public void Attack(Monster monster)
  25:         {
  26:             if (monster.HP <= 0)
  27:             {
  28:                 Console.WriteLine("此怪物已死");
  29:                 return;
  30:             }
  31:  
  32:             if ("WoodSword" == this.WeaponTag)
  33:             {
  34:                 monster.HP -= 20;
  35:                 if (monster.HP <= 0)
  36:                 {
  37:                     Console.WriteLine("***成功!怪物" + monster.Name + "已死亡");
  38:                 }
  39:                 else
  40:                 {
  41:                     Console.WriteLine("***成功!怪物" + monster.Name + "損失20HP");
  42:                 }
  43:             }
  44:             else if ("IronSword" == this.WeaponTag)
  45:             {
  46:                 monster.HP -= 50;
  47:                 if (monster.HP <= 0)
  48:                 {
  49:                     Console.WriteLine("***成功!怪物" + monster.Name + "已死亡");
  50:                 }
  51:                 else
  52:                 {
  53:                     Console.WriteLine("***成功!怪物" + monster.Name + "損失50HP");
  54:                 }
  55:             }
  56:             else if ("MagicSword" == this.WeaponTag)
  57:             {
  58:                 Int32 loss = (_random.NextDouble() < 0.5) ? 100 : 200;
  59:                 monster.HP -= loss;
  60:                 if (200 == loss)
  61:                 {
  62:                     Console.WriteLine("出現暴擊!!!");
  63:                 }
  64:  
  65:                 if (monster.HP <= 0)
  66:                 {
  67:                     Console.WriteLine("***成功!怪物" + monster.Name + "已死亡");
  68:                 }
  69:                 else
  70:                 {
  71:                     Console.WriteLine("***成功!怪物" + monster.Name + "損失" + loss + "HP");
  72:                 }
  73:             }
  74:             else
  75:             {
  76:                 Console.WriteLine("角色手裏沒有武器,無法***!");
  77:             }
  78:         }
  79:     }
  80: }

Code:測試代碼

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5:  
   6: namespace IGameLi
   7: {
   8:     class Program
   9:     {
  10:         static void Main(string[] args)
  11:         {
  12:             //生成怪物
  13:             Monster monster1 = new Monster("小怪A", 50);
  14:             Monster monster2 = new Monster("小怪B", 50);
  15:             Monster monster3 = new Monster("關主", 200);
  16:             Monster monster4 = new Monster("最終Boss", 1000);
  17:  
  18:             //生成角色
  19:             Role role = new Role();
  20:  
  21:             //木劍***
  22:             role.WeaponTag = "WoodSword";
  23:             role.Attack(monster1);
  24:  
  25:             //鐵劍***
  26:             role.WeaponTag = "IronSword";
  27:             role.Attack(monster2);
  28:             role.Attack(monster3);
  29:  
  30:             //魔劍***
  31:             role.WeaponTag = "MagicSword";
  32:             role.Attack(monster3);
  33:             role.Attack(monster4);
  34:             role.Attack(monster4);
  35:             role.Attack(monster4);
  36:             role.Attack(monster4);
  37:             role.Attack(monster4);
  38:  
  39:             Console.ReadLine();
  40:         }
  41:     }
  42: }

 

程序運行結果如下:

圖1.2 小李程序的運行結果

1.3 架構師的建議

小李闡述完自己的想法並演示了Demo後,項目組長Peter首先肯定了小李的思考能力、編程能力以及初步的面向對象分析與設計的思想,並承認小李的程序正確完成了需求中的功能。但同時,Peter也指出小李的設計存在一些問題,他請小於講一下自己的看法。

小於是一名有五年軟件架構經驗的架構師,對軟件架構、設計模式和麪向對象思想有較深入的認識。他向Peter點了點頭,發表了自己的看法:

“小李的思考能力是不錯的,有着基本的面向對象分析設計能力,並且程序正確完成了所需要的功能。不過,這裏我想從架構角度,簡要說一下我認爲這個設計中存在的問題。

首先,小李設計的Role類的Attack方法很長,並且方法中有一個冗長的if…else結構,且每個分支的代碼的業務邏輯很相似,只是很少的地方不同

再者,我認爲這個設計比較大的一個問題是,違反了OCP原則。在這個設計中,如果以後我們增加一個新的武器,如倚天劍,每次***損失500HP,那麼,我們就要打開Role,修改Attack方法。而我們的代碼應該是對修改關閉的,當有新武器加入的時候,應該使用擴展完成,避免修改已有代碼

一般來說,當一個方法裏面出現冗長的if…else或switch…case結構,且每個分支代碼業務相似時,往往預示這裏應該引入多態性來解決問題。而這裏,如果把不同武器***看成一個策略,那麼引入策略模式(Strategy Pattern)是明智的選擇。

最後說一個小的問題,被***後,減HP、死亡判斷等都是怪物的職責,這裏放在Role中有些不當。”

Tip:OCP原則,即開放關閉原則,指設計應該對擴展開放,對修改關閉。

Tip:策略模式,英文名Strategy Pattern,指定義算法族,分別封裝起來,讓他們之間可以相互替換,此模式使得算法的變化獨立於客戶。

小於邊說,邊畫了一幅UML類圖,用於直觀表示他的思想。

圖1.3 小於的設計

Peter讓小李按照小於的設計重構Demo,小李看了看小於的設計圖,很快完成。相關代碼如下:

Code: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: }

 

Code:木劍

   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: }

 

Code:鐵劍

   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: }

 

Code:魔劍

   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: }

 

Code:怪物

   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: }

 

Code:角色

   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: }

 

Code:測試代碼

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5:  
   6: namespace IGameLiAdv
   7: {
   8:     class Program
   9:     {
  10:         static void Main(string[] args)
  11:         {
  12:             //生成怪物
  13:             Monster monster1 = new Monster("小怪A", 50);
  14:             Monster monster2 = new Monster("小怪B", 50);
  15:             Monster monster3 = new Monster("關主", 200);
  16:             Monster monster4 = new Monster("最終Boss", 1000);
  17:  
  18:             //生成角色
  19:             Role role = new Role();
  20:  
  21:             //木劍***
  22:             role.Weapon = new WoodSword();
  23:             role.Attack(monster1);
  24:  
  25:             //鐵劍***
  26:             role.Weapon = new IronSword();
  27:             role.Attack(monster2);
  28:             role.Attack(monster3);
  29:  
  30:             //魔劍***
  31:             role.Weapon = new MagicSword();
  32:             role.Attack(monster3);
  33:             role.Attack(monster4);
  34:             role.Attack(monster4);
  35:             role.Attack(monster4);
  36:             role.Attack(monster4);
  37:             role.Attack(monster4);
  38:  
  39:             Console.ReadLine();
  40:         }
  41:     }
  42: }

 

編譯運行以上代碼,得到的運行結果與上一版本代碼基本一致。

1.4 小李的小結

Peter顯然對改進後的代碼比較滿意,他讓小李對照兩份設計和代碼,進行一個小結。小李簡略思考了一下,並結合小於對一次設計指出的不足,說道:

“我認爲,改進後的代碼有如下優點:

第一,雖然類的數量增加了,但是每個類中方法的代碼都非常短,沒有了以前Attack方法那種很長的方法,也沒有了冗長的if…else,代碼結構變得很清晰

第二,類的職責更明確了。在第一個設計中,Role不但負責***,還負責給怪物減少HP和判斷怪物是否已死。這明顯不應該是Role的職責,改進後的代碼將這兩個職責移入Monster內,使得職責明確,提高了類的內聚性。

第三,引入Strategy模式後,不但消除了重複性代碼,更重要的是,使得設計符合了OCP。如果以後要加一個新武器,只要新建一個類,實現IAttackStrategy接口,當角色需要裝備這個新武器時,客戶代碼只要實例化一個新武器類,並賦給Role的Weapon成員就可以了,已有的Role和Monster代碼都不用改動。這樣就實現了對擴展開發,對修改關閉。”

Peter和小於聽後都很滿意,認爲小李總結的非常出色。

IGame公司的討論會還在進行着,內容是非常精彩,不過我們先聽到這裏,因爲,接下來,我們要對其中某些問題進行一點探討。別忘了,本文的主題可是依賴注入,這個主角還沒登場呢!讓主角等太久可不好。

2 探究依賴注入

2.1 故事的啓迪

我們現在靜下心來,再回味一下剛纔的故事。因爲,這個故事裏面隱藏着依賴注入的出現原因。我說過不只一次,想真正認清一個事物,不能只看“它是什麼?什麼樣子?”,而應該先弄清楚“它是怎麼來的?是什麼樣的需求和背景促使了它的誕生?它被創造出來是做什麼用的?”。

回想上面的故事。剛開始,主要需求是一個打怪的功能。小李做了一個初步面向對象的設計:抽取領域場景中的實體(怪物、角色等),封裝成類,併爲各個類賦予屬性與方法,最後通過類的交互完成打怪功能,這應該算是面向對象設計的初級階段。

在小李的設計基礎上,架構師小於指出了幾點不足,如不符合OCP,職責劃分不明確等等,並根據情況引入策略模式。這是更高層次的面向對象設計。其實就核心來說,小於只做了一件事:利用多態性,隔離變化。它清楚認識到,這個打怪功能中,有些業務邏輯是不變的,如角色***怪物,怪物減少HP,減到0怪物就會死;而變化的僅僅是不同的角色持有不同武器時,每次***的效用不一樣。於是他的架構,本質就是把變化的部分和不變的部分隔離開,使得變化部分發生變化時,不變部分不受影響。

我們再仔細看看小於的設計圖,這樣設計後,有個基本的問題需要解決:現在Role不依賴具體武器,而僅僅依賴一個IAttackStrategy接口,接口是不能實例化的,雖然Role的Weapon成員類型定義爲IAttackStrategy,但最終還是會被賦予一個實現了IAttackStrategy接口的具體武器,並且隨着程序進展,一個角色會裝備不同的武器,從而產生不同的效用。賦予武器的職責,在Demo中是放在了測試代碼裏。

這裏,測試代碼實例化一個具體的武器,並賦給Role的Weapon成員的過程,就是依賴注入!這裏要清楚,依賴注入其實是一個過程的稱謂!

2.2 正式定義依賴注入

下面,用稍微正式一點的語言,定義依賴注入產生的背景緣由和依賴注入的含義。在讀的過程中,讀者可以結合上面的例子進行理解。

依賴注入產生的背景:

隨着面向對象分析與設計的發展,一個良好的設計,核心原則之一就是將變化隔離,使得變化部分發生變化時,不變部分不受影響(這也是OCP的目的)。爲了做到這一點,要利用面向對象中的多態性,使用多態性後,客戶類不再直接依賴服務類,而是依賴於一個抽象的接口,這樣,客戶類就不能在內部直接實例化具體的服務類。但是,客戶類在運作中又客觀需要具體的服務類提供服務,因爲接口是不能實例化去提供服務的。就產生了“客戶類不準實例化具體服務類”和“客戶類需要具體服務類”這樣一對矛盾。爲了解決這個矛盾,開發人員提出了一種模式:客戶類(如上例中的Role)定義一個注入點(Public成員Weapon),用於服務類(實現IAttackStrategy的具體類,如WoodSword、IronSword和MagicSword,也包括以後加進來的所有實現IAttackStrategy的新類)的注入,而客戶類的客戶類(Program,即測試代碼)負責根據情況,實例化服務類,注入到客戶類中,從而解決了這個矛盾。

依賴注入的正式定義:

依賴注入(Dependency Injection),是這樣一個過程:由於某客戶類只依賴於服務類的一個接口,而不依賴於具體服務類,所以客戶類只定義一個注入點。在程序運行過程中,客戶類不直接實例化具體服務類實例,而是客戶類的運行上下文環境或專門組件負責實例化服務類,然後將其注入到客戶類中,保證客戶類的正常運行。

3 依賴注入那些事兒

上面我們從需求背景的角度,講述了依賴注入的來源和定義。但是,如果依賴注入僅僅就只有這麼點東西,那也沒有什麼值得討論的了。但是,上面討論的僅僅是依賴注入的內涵,其外延還是非常廣泛的,從依賴注入衍生出了很多相關的概念與技術,下面我們討論一下依賴注入的“那些事兒”。

3.1 依賴注入的類別

依賴注入有很多種方法,上面看到的例子中,只是其中的一種,下面分別討論不同的依賴注入類型。

3.1.1 Setter注入

第一種依賴注入的方式,就是Setter注入,上面的例子中,將武器注入Role就是Setter注入。正式點說:

Setter注入(Setter Injection)是指在客戶類中,設置一個服務類接口類型的數據成員,並設置一個Set方法作爲注入點,這個Set方法接受一個具體的服務類實例爲參數,並將它賦給服務類接口類型的數據成員。

圖3.1 Setter注入示意

上圖展示了Setter注入的結構示意圖,客戶類ClientClass設置IServiceClass類型成員_serviceImpl,並設置Set_ServiceImpl方法作爲注入點。Context會負責實例化一個具體的ServiceClass,然後注入到ClientClass裏。

下面給出Setter注入的示例代碼。

Code:IServiceClass
   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5:  
   6: namespace SetterInjection
   7: {
   8:     internal interface IServiceClass
   9:     {
  10:         String ServiceInfo();
  11:     }
  12: }

 

Code:ServiceClassA
   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5:  
   6: namespace SetterInjection
   7: {
   8:     internal class ServiceClassA : IServiceClass
   9:     {
  10:         public String ServiceInfo()
  11:         {
  12:             return "我是ServceClassA";
  13:         }
  14:     }
  15: }

 

Code:ServiceClassB
   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5:  
   6: namespace SetterInjection
   7: {
   8:     internal class ServiceClassB : IServiceClass
   9:     {
  10:         public String ServiceInfo()
  11:         {
  12:             return "我是ServceClassB";
  13:         }
  14:     }
  15: }

 

Code:ClientClass
   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5:  
   6: namespace SetterInjection
   7: {
   8:     internal class ClientClass
   9:     {
  10:         private IServiceClass _serviceImpl;
  11:  
  12:         public void Set_ServiceImpl(IServiceClass serviceImpl)
  13:         {
  14:             this._serviceImpl = serviceImpl;
  15:         }
  16:  
  17:         public void ShowInfo()
  18:         {
  19:             Console.WriteLine(_serviceImpl.ServiceInfo());
  20:         }
  21:     }
  22: }

原文地址:http://www.cnblogs.com/leoo2sk/archive/2009/06/17/1504693.html

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章