1. 程式人生 > >C#依賴注入那些事兒(一)

C#依賴注入那些事兒(一)

1 IGame遊戲公司的故事

1.1 討論會

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

圖1.1 需求描述ppt

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

1.2 實習生小李的實現方式

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

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

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

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

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace IGameLi
{
    /// <summary>
    /// 怪物
    /// </summary>
    internal sealed class Monster
    {
        /// <summary>
        /// 怪物的名字
        /// </summary>
        public String Name { get; set; }
 
        /// <summary>
        /// 怪物的生命值
        /// </summary>
        public Int32 HP { get; set; }
 
        public Monster(String name,Int32 hp)
        {
            this.Name = name;
            this.HP = hp;
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace IGameLi
{
    /// <summary>
    /// 角色
    /// </summary>
    internal sealed class Role
    {
        private Random _random = new Random();
 
        /// <summary>
        /// 表示角色目前所持武器的字串
        /// </summary>
        public String WeaponTag { get; set; }
 
        /// <summary>
        /// 攻擊怪物
        /// </summary>
        /// <param name="monster">被攻擊的怪物</param>
        public void Attack(Monster monster)
        {
            if (monster.HP <= 0)
            {
                Console.WriteLine("此怪物已死");
                return;
            }
 
            if ("WoodSword" == this.WeaponTag)
            {
                monster.HP -= 20;
                if (monster.HP <= 0)
                {
                    Console.WriteLine("攻擊成功!怪物" + monster.Name + "已死亡");
                }
                else
                {
                    Console.WriteLine("攻擊成功!怪物" + monster.Name + "損失20HP");
                }
            }
            else if ("IronSword" == this.WeaponTag)
            {
                monster.HP -= 50;
                if (monster.HP <= 0)
                {
                    Console.WriteLine("攻擊成功!怪物" + monster.Name + "已死亡");
                }
                else
                {
                    Console.WriteLine("攻擊成功!怪物" + monster.Name + "損失50HP");
                }
            }
            else if ("MagicSword" == this.WeaponTag)
            {
                Int32 loss = (_random.NextDouble() < 0.5) ? 100 : 200;
                monster.HP -= loss;
                if (200 == loss)
                {
                    Console.WriteLine("出現暴擊!!!");
                }
 
                if (monster.HP <= 0)
                {
                    Console.WriteLine("攻擊成功!怪物" + monster.Name + "已死亡");
                }
                else
                {
                    Console.WriteLine("攻擊成功!怪物" + monster.Name + "損失" + loss + "HP");
                }
            }
            else
            {
                Console.WriteLine("角色手裡沒有武器,無法攻擊!");
            }
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace IGameLi
{
    class Program
    {
        static void Main(string[] args)
        {
            //生成怪物
            Monster monster1 = new Monster("小怪A", 50);
            Monster monster2 = new Monster("小怪B", 50);
            Monster monster3 = new Monster("關主", 200);
            Monster monster4 = new Monster("最終Boss", 1000);
 
            //生成角色
            Role role = new Role();
 
            //木劍攻擊
            role.WeaponTag = "WoodSword";
            role.Attack(monster1);
 
            //鐵劍攻擊
            role.WeaponTag = "IronSword";
            role.Attack(monster2);
            role.Attack(monster3);
 
            //魔劍攻擊
            role.WeaponTag = "MagicSword";
            role.Attack(monster3);
            role.Attack(monster4);
            role.Attack(monster4);
            role.Attack(monster4);
            role.Attack(monster4);
            role.Attack(monster4);
 
            Console.ReadLine();
        }
    }
}

程式執行結果如下:

圖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,小李看了看小於的設計圖,很快完成。相關程式碼如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace IGameLiAdv
{
    internal interface IAttackStrategy
    {
        void AttackTarget(Monster monster);
    }
}
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(20);
        }
    }
}
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(50);
        }
    }
}
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) ? 100 : 200;
            if (200 == loss)
            {
                Console.WriteLine("出現暴擊!!!");
            }
            monster.Notify(loss);
        }
    }
}
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 <= 0)
            {
                Console.WriteLine("此怪物已死");
                return;
            }
 
            this.HP -= loss;
            if (this.HP <= 0)
            {
                Console.WriteLine("怪物" + this.Name + "被打死");
            }
            else
            {
                Console.WriteLine("怪物" + this.Name + "損失" + loss + "HP");
            }
        }
    }
}
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);
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace IGameLiAdv
{
    class Program
    {
        static void Main(string[] args)
        {
            //生成怪物
            Monster monster1 = new Monster("小怪A", 50);
            Monster monster2 = new Monster("小怪B", 50);
            Monster monster3 = new Monster("關主", 200);
            Monster monster4 = new Monster("最終Boss", 1000);
 
            //生成角色
            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();
        }
    }
}

編譯執行以上程式碼,得到的執行結果與上一版本程式碼基本一致。

1.4 小李的小結

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

“我認為,改進後的程式碼有如下優點:

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

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

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

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

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

相關推薦

C#依賴注入那些事兒

1 IGame遊戲公司的故事 1.1 討論會 話說有一個叫IGame的遊戲公司,正在開發一款ARPG遊戲(動作&角色扮演類遊戲,如魔獸世界、夢幻西遊這一類的遊戲)。一般這類遊戲都有一個基本的功能,就是打怪(玩家攻擊怪物,藉此獲得經驗、虛擬貨幣和虛擬裝備

關於C++的那些事兒--讀《寫給大忙人看的C++》

註釋的使用 /* / 是從一個(/ )開始到 ( */)結束的。 重置輸入流的錯誤標誌,需要呼叫cin.clear()。 int x=static_cast(0.0); ==型別轉換== 從不建議在表示式中組合==有==符號整數和無符號整數。因為無符號整數u

關於搶火車票的那些事兒

關於搶火車票的那些事兒(一) 又到了年底了,沒到這個時候外出工作的人兒又開始犯愁了火車票的事兒~ 票不好買,於是黃牛呀,各種搶票軟體走進了大家的事業。 搶票軟體到底是怎麼做的呢? 正好最近工作不忙,我也比較感興趣,那就來探究一下。

DeepFM演算法解析及Python實現 FFM演算法解析及Python實現 FM演算法解析及Python實現 詞嵌入的那些事兒

1. DeepFM演算法的提出 由於DeepFM演算法有效的結合了因子分解機與神經網路在特徵學習中的優點:同時提取到低階組合特徵與高階組合特徵,所以越來越被廣泛使用。 在DeepFM中,FM演算法負責對一階特徵以及由一階特徵兩兩組合而成的二階特徵進行特徵的提取;DNN演算法負責對由輸入的一階特徵進行全連線

移動端那些事兒移動端開發注意事項

對於手機網站建設,總結了如下幾點注意: 1、 安卓瀏覽器看背景圖片,有些裝置會模糊。 用同等比例的圖片在PC機上很清楚,但是手機上很模糊,原因是什麼呢? 經過研究,是devicePixelRatio作怪,因為手機解析度太小,如果按照解析度來顯示網頁,這樣字會非常小,所以蘋果當初就把iPhone 4的9

【JAVA秒會技術之玩轉多執行緒】多執行緒那些事兒

多執行緒那些事兒(一) 現在只要出去面試,關於“Java多執行緒”的問題,幾乎沒有一家單位不問的,可見其重要性。於是博主抽空研究了一下,確實很有意思!以下是我綜合整理了網上的各種資料,和個人的一些理解,寫的一篇總結博文,僅供學習、交流。 (一)多執行緒的概念      

網路中的那些事兒之神奇的通訊

       考試大戰在即,這是大二生涯中最後一場戰役,為了給自己這半年的戎馬生涯畫上一個圓滿句號,故最近幾天在複習(其實說預習更為合理一些)《計算機網路》這門課程。臨時抱佛腳,真心地不好。不過好在自

EditText 的那些事兒

針對EditText 的相關知識點整理一下,主要有EditText的相關屬性,EditText的自定義VIew方面以及連帶的其他知識點;一為EditText的相關屬性及連帶知識點,後面寫寫自定義view繼承EditText, 比如密碼輸入框(帶刪除,明文暗文按鈕),更強大的autoCompl

計算機中那些事兒:妙用遠端,方便你我他!

你還在為忘記電腦關機,傳檔案忘帶u盤,急著操作電腦而不在電腦旁,,,而苦惱嗎??? 如果是,那恭喜您,以下的方法一定會接觸您的煩惱!如果不是,那也恭喜您,一起下去看看吧下面的內容一定會讓您解除將來的煩

鄧仰東專欄|機器學習的那些事兒

目錄 1.緒論 1.1.概述 1.2 機器學習簡史 1.3 機器學習改變世界:基於GPU的機器學習例項      1.3.1 基於深度神經網路的視覺識別      1.3.2 AlphaGO      1.3.3 IBM Waston 1.4 機器學習方

關於androidstudio cmake那些事兒

關於在as上使用cmake開發NDK,還沒有比較詳細統一的資料,就個人踩過的一些坑記一下,我們在android NDK開發的時候大部分是在windows下面進行開發,在windows下面編譯用標準c++庫的一些檔案,然後又要編譯成so的時候,這個時候就比較坑了。

依賴注入Dependency Injection

Let's say we have a CustomerEditScreen that needs to load a Customer entity by ID from a web servi

Shiro那些事兒: Shiro初探

 引言   許可權,可以簡單的理解成你能幹什麼,不能幹什麼。在管理系統中,對許可權的設計可以很簡單,也可以很複雜。簡單點的,基本都是基於角色扮演的方式,比如系統管理員角色可以操作哪些選單,普通使用者角色可以操作哪些選單等等,通過讓不同使用者扮演不同的角色,不同角色授予不同的選單許可權,來實現對訪問使

C語言的那些事兒 迴圈結構程式設計

1.迴圈語句初認識:在不少實際問題中有許多具有規律性的重複操作,因此在程式中就需要重複執行某些語句。一組被重複執行的語句稱之為迴圈體,能否繼續重複,決定迴圈的終止條件。迴圈結構是在一定條件下反覆執行某段程式的流程結構,被反覆執行的程式被稱為迴圈體。迴圈語句是由迴圈體及迴圈的終止條件兩部

C++數值與字串相互轉換的那些字串轉數值(轉載請註明)

以前一門心思搞演算法,這個東西覺得自己寫個函式就能實現的事,但是到了公司後才發現同事寫的程式碼裡面,呼叫各種庫函式、window API、流來實現。什麼都不懂的我表示鴨梨很大,今天翻了翻資料瞭解了下各種方法的使用方法、區別以及適用範圍,寫成了這篇又長又臭又沒條理的東西。 注

計算機中那些事兒:讓你的計算機飛般的感覺

  【前言】     在當今這個IT時代(當然正向 DT時代過渡)計算機已經成為人們的必備物品之一。正所謂:工欲善其事必先利其器。今天咱們就一起來聊聊如何讓咱們的電腦有飛一般的感覺。 【我記憶

Objective-C語法之第一個iPhone應用程式的那些事兒

#import "HelloWorldViewController.h" @implementation HelloWorldViewController - (void)didReceiveMemoryWarning { // Releases the view if it doesn't ha

C#冷知識系列】那些你知道或者不知道的奇淫巧技

愛的 讓我 同事 orm lec 工程師 能夠 代碼 優勢 引子 正如我在個人介紹中所寫,我是一個仍然堅持.NET的頭鐵高級軟件工程師,研究C#,.NET已經六年多,一直堅持認為自己的能力不足以教授別人,所以一直沒有想法寫博客。工作幾年,內容涵蓋了.NET框架下的各種軟件的

C++ 運算符重載

c++ operator 運算符重載前景 : 在設計模式裏面存在觀察者模式(主題只有一個 , 訂閱著(訂閱主題)有很多個 )。在一些語言中叫事件發送/事件偵聽 比如AS3 。在C#也有類似的概念 , 不過它重載了+/-用於訂閱和取消訂閱的計算。這裏重載+/-用於訂閱或取消訂閱確實比AS3的 addEventL

html頁面中拍照和上傳照片那些事兒

read itl 加載完成 大小 上傳照片 那些事 cnblogs 設置 新建 本文為原創,轉載請註明出處: cnzt 文章:cnzt-p http://www.cnblogs.com/zt-blog/p/6895352.html 本文主要說下iOS上