2018年7月8日 星期日

C# Switch 重構 Refactoring 3 : 使用策略設計模式與簡易相依注入

在上一篇文章中, 
C# Switch 重構 Refactoring 2 : 使用資料字典與多型或委派方法或 Lambda
,我們嘗試使用資料字典,搭配抽象類別的繼承方法,將複雜的 Switch 敘述,透過資料字典的方式,重構這個 Switch 需要做到的功能,我們可以看得出來,透過這樣的重構過程,一旦我們有了新的需求,我們就可以直接透過新建立一個繼承抽象類別的類別,將新的需求撰寫在這個新類別中,這也就是 OCP 所提到 開放延伸的功能。可是,我們發現到了,當我們新建立一個類別之後,我們需要在原有的類別 StringToColor ,多增加一筆資料字典記錄到 _colorMaps 物件內,這樣,我們才可以使用這個新增加的顏色轉換,不過,這也違反了 OCP 的封閉修改的原則 (您可以嘗試使用一個工廠方法來改善這樣的問題,請您自行練習看看)。現在,在這篇文章之中,我們將要透過策略設計模式開發建議,套用這個設計模式,讓我們這個類別 StringToColor 可以符合 OCP 原則,因此,我們將會得到一個技能,若您的程式碼符合策略設計模式需求,您就可以套用這個設計模式,讓您的類別可以具有 OCP 帶來的好處。
在這裡,我會建立 RefactorSwitchStrategy 專案來進行說明 (這裡的練習專案,將會建立一個 .NET Core 主控制台應用程式專案)
首先,我們原來使用 Switch 關鍵字的程式碼如下,我們有個類別 StringToColor,裡面僅有一個方法 Transfer,在此方法內,使用 Switch 來判斷傳入的字串顏色名稱,產生出該顏色的 Color 類別物件。
C Sharp / C#
public class StringToColor
{
    public Color Transfer(string name)
    {
        Color transferResult;

        switch (name.ToLower())
        {
            case "red":
                transferResult = Color.FromArgb(0xFF, 0xFF, 0x00, 0x00);
                break;
            case "green":
                transferResult = Color.FromArgb(0xFF, 0x80, 0x80, 0x80);
                break;
            case "blue":
                transferResult = Color.FromArgb(0xFF, 0x00, 0x00, 0xFF);
                break;
            default:
                throw new ArgumentException("不正確的顏色名稱");
        }
        return transferResult;
    }
}
在這裡,我們來看看如何使用這個支援類別的將字串轉換成為 Color 類別物件。
首先,我們建立一個 StringToColor 類別的物件,緊接著呼叫 Transfer 方法,我們在這個方法傳入一個字串 (Red) 進去,當然,我們將會取得一個 Color 物件,並且把這個物件的顏色屬性顯示在螢幕上。
C Sharp / C#
class Program
{
    static void Main(string[] args)
    { 
        string MyColorName = "Blue";

        Console.WriteLine("使用 Swith 來設計方法");

        StringToColor foo = new StringToColor();
        Color fooColor = foo.Transfer(MyColorName);

        Console.WriteLine($"{MyColorName} =  A:{fooColor.A} R:{fooColor.R} G:{fooColor.G} B:{fooColor.B}");
        Console.WriteLine("Press any key for continuing...");
    }
}
這裡是執行結果
Console
使用 Swith 來設計方法
Blue =  A:255 R:0 G:0 B:255
Press any key for continuing...
首先,我們還是需要分別為各個顏色轉換需求,建立一個新的類別,將這些特定顏色轉換邏輯,分批寫到這些類別內,不過,在此之前,我們同樣需要先設計一個抽象類別 ColorTransfer,在這個抽象類別中,分別存在這一個抽象唯讀屬性,用來標示當時實作類別是要使用哪個顏色字串來使用,另外一個是抽象方法,那就是要將顏色轉換的方法,我們需要在分別顏色轉換類別中,將顏色轉換邏輯,撰寫到這個方法內。
完成之後,程式碼將會如下所示,我們以轉換 Red 字串成為紅色 Color 物件這個類別 TransferToRed 為例,他繼承了 ColorTransfer 抽象類別,因此需要實作抽象屬性與抽象方法,我們在抽象屬性 ColorName 設定其值為 "Red",而在抽象方法 Transfer 內,產生一個紅色的 Color 物件;而其他的顏色,也依照這樣的規則進行設計。
一旦完成這樣的重構之後,您會發現到,每個顏色轉換的類別程式碼相當的清爽與明確,我們一看程式碼,就知道這個類別要提供甚麼樣的服務,若發生產生的 Color 顏色不正確,我們也可以很快地從 Transfer 方法內,進行錯誤修正;這比起之前的 Switch ... case 的長串敘述,是不是更容易閱讀與維護了呢?
C Sharp / C#
namespace RefactorSwitchStrategy.Refactoring
{
    public abstract class ColorTransfer
    {
        public abstract Color Transfer();
        public abstract string ColorName { get; }
    }
    public class TransferToRed : ColorTransfer
    {
        public override string ColorName => "Red";

        public override Color Transfer()
        {
            return Color.FromArgb(0xFF, 0xFF, 0x00, 0x00);
        }
    }
    public class TransferToGreen : ColorTransfer
    {
        public override string ColorName => "Green";
        public override Color Transfer()
        {
            return Color.FromArgb(0xFF, 0x80, 0x80, 0x80);
        }
    }
    public class TransferToBlue : ColorTransfer
    {
        public override string ColorName => "Blue";
        public override Color Transfer()
        {
            return Color.FromArgb(0xFF, 0x00, 0x00, 0xFF);
        }
    }
}
現在,我們還需要來建立一個簡易相依注入的類別服務,我們先定義一個介面 IColorTranserService,在這個介面中,僅宣告一個 Transfer 方法。將著,我們將實作出這個介面,我們將會設計出類別 ColorTransferServce,在此類別中,我們定義一個集合 List 物件 _colorMaps,並且將所有可支援顏色轉換的類別,接產生出他們的物件到這個 _colorMaps 集合中,這個集合中的每個項目 Item 的型別為強型別的 ColorTransfer,也就是我們剛剛所建立的抽象類別,可以這麼做的原因是,每個顏色轉換物件,都是繼承於這個抽象類別,基於多型的規則,我們就可以把子型別產生的物件,設定給傅型別的變數中。
在這個實作介面的 ColorTransferServce 類別中,我們還需要實作出方法 Transfer,這個方法接收一個參數,也就是要轉換的顏色的字串。我們透過 LINQ 的 FirstOrDefault 方法,找出這些顏色轉換實作類別的物件中,有相同顏色字串的物件出來,並且執行這些顏色轉換的類別中定義的顏色轉換商業邏輯,一旦完成之後,我們就會得到該字串所相對應的 Color 物件。
namespace RefactorSwitchStrategy.Refactoring
{
    public interface IColorTranserService
    {
        Color Transfer(string name);
    }
    public class ColorTransferServce : IColorTranserService
    {
        List<ColorTransfer> _colorMaps;
        public ColorTransferServce()
        {
            _colorMaps = new List<ColorTransfer>()
            {
                {new TransferToRed() },
                {new TransferToGreen() },
                {new TransferToBlue() },
            };

        }
        public Color Transfer(string name)
        {
            return _colorMaps.FirstOrDefault(x => x.ColorName == name).Transfer();
        }
    }
}
現在我們要來修正我們最初的 StringToColor 類別,並且讓這個類別能夠符合 OCP 原則,也就是日後若有新的顏色文字要轉換成為指定的 Color 物件,我們僅需要新增一個繼承於 ColorTransfer 抽象類別的新類別與新增一筆該類別的物件,到上述的 _colorMaps 變數中即可,這樣,我們便可以藉由新增類別來擴展新功能,並且關閉了 StringToColor 類別的修改,達成需求變更的目的。
在 StringToColor 類別內,我們增加兩個建構式,其中若預設建構式將會呼叫另外一個建構式,另外一個建構式,將會透過建構式注入的方式,取得一個 IColorTranserService 界面實作物件,所以,我們就可以在 Transfer 方法內,直接使用這個 IColorTranserService.Transfer(name) 方法,進行顏色字串轉換成為 Color 物件的需求了。
C Sharp / C#
namespace RefactorSwitchStrategy.Refactoring
{
    public class StringToColor
    {
        IColorTranserService _colorTranserService;
        Dictionary<string, Color> ColorMaps;
        public StringToColor(IColorTranserService colorTranserService)
        {
            _colorTranserService = colorTranserService;
        }
        public StringToColor() : this(new ColorTransferServce())
        {
        }
        public Color Transfer(string name)
        {
            return _colorTranserService.Transfer(name);
        }
    }
    #endregion
}
這是我們在主程式中的測試程式碼
C Sharp / C#
class Program
{
    static void Main(string[] args)
    { 
        string MyColorName = "Blue";

            Console.WriteLine("使用 資料字典與多型 來重構方法,進行 Swith 需求設計");

            RefactorSwitchDictionary.Refactoring.StringToColor fooRefactoring = new RefactorSwitchDictionary.Refactoring.StringToColor();
            Color fooRefactoringColor = fooRefactoring.Transfer(MyColorName);

            Console.WriteLine($"{MyColorName} =  A:{fooRefactoringColor.A} R:{fooRefactoringColor.R} G:{fooRefactoringColor.G} B:{fooRefactoringColor.B}");
            Console.WriteLine("Press any key for continuing...");
            Console.ReadKey();
    }
}
執行結果內容
Console
使用 使用策略設計模式與簡易相依注入 來重構方法,進行 Swith 需求設計
Blue =  A:255 R:0 G:0 B:255
Press any key for continuing...
最後,您可以看出這樣重構之後,新的程式碼具有那些好處與優點,並且存在著那些其他問題嗎?

關於 Xamarin 在台灣的學習技術資源

Xamarin 實驗室 粉絲團
歡迎加入 Xamarin 實驗室 粉絲團,在這裡,將會經常性的貼出各種關於 Xamarin / Visual Studio / .NET 的相關消息、文章、技術開發等文件,讓您可以隨時掌握第一手的 Xamarin 方面消息。
Xamarin.Forms @ Taiwan
歡迎加入 Xamarin.Forms @ Taiwan,這是台灣的 Xamarin User Group,若您有任何關於 Xamarin / Visual Studio / .NET 上的問題,都可以在這裡來與各方高手來進行討論、交流。
Xamarin 實驗室 部落格
Xamarin 實驗室 部落格 是作者本身的部落格,這個部落格將會專注於 Xamarin 之跨平台 (Android / iOS / UWP) 方面的各類開技術探討、研究與分享的文章,最重要的是,它是全繁體中文。
Xamarin.Forms 系列課程
Xamarin.Forms 系列課程 想要快速進入到 Xamarin.Forms 的開發領域,學會各種 Xamarin.Forms 跨平台開發技術,例如:MVVM、Prism、Data Binding、各種 頁面 Page / 版面配置 Layout / 控制項 Control 的用法等等,千萬不要錯過這些 Xamarin.Forms 課程



沒有留言:

張貼留言