2018年7月9日 星期一

如何進行您的專案程式的 SOLID Principle 原則之評估方法

在這篇文章中,我們將分別針對 SOLID 的五個原則,說明如何檢驗您的程式碼,是否有符合這些原則的檢驗論述指引。也就是說,當您判斷一個程式,是否有符合 SOLID 各個原則或者其中一個原則,您可以使用這篇文章提出的指引,描述出您的看法。

Single Responsibility Principle SRP

在 Wiki 定義 Single Responsibility Principle 如下
every module or class should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class.
而在 Robert C. Martin 對於這個原則的描述為
A class should have only one reason to change.

驗證步驟

在這裡,我們將需要進行 分析對象 與 分析理由 的論述

分析對象

我們在這裡採用 Robert C. Martin 的描述,因此,請您要指出要分析的類別是哪一個,也就是: 一個類別應該只有一個改變的理由!
類別 C

分析理由

因為 responsibility as a reason to change 責任將會定義為一個改變的理由,而要改變這個需求,有些時候會提早知道,而大部分的時候,您幾乎無法做到預測,這些變更的理由可能會來自於客戶的要求、PM的修正、當時環境的變更、公司的政策等等。
所以,請在這裡描述您認為這個類別究竟存在哪些責任,為什麼會有這樣的責任存在呢?
在類別 C 中,存在著 R 責任,會有 R 責任存在,是因為存在著 RC 變更理由
經過重構之後,在類別 C 中,僅存在著一個變更理由 (RC'),也就是具有一個責任 (R')
請依序把上述的 C, R , RC , RC' , R' 替換您的分析理由,若有多個責任,請分別描述出來

Open Closed Principle OCP

在 Wiki 定義 Open Closed Principle 如下
software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification, that is, such an entity can allow its behaviour to be extended without modifying its source code.
在 Uncle Bob 的書中,對於 OCP ,定義 OCP 如下
Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.
在最初的 OCP 提出者 Bertrand Meyer,定義 OCP 如下
A module will be said to be open if it is still available for extension. For example, it should be possible to add fields to the data structures it contains, or new elements to the set of functions it performs.
A module will be said to be closed if [it] is available for use by other modules. This assumes that the module has been given a well-defined, stable description (the interface in the sense of information hiding).[3]
Meyer's proposed solution to this dilemma relied on the notion of object-oriented inheritance (specifically implementation inheritance):
A class is closed, since it may be compiled, stored in a library, baselined, and used by client classes. But it is also open, since any new class may use it as parent, adding new features. When a descendant class is defined, there is no need to change the original or to disturb its clients.

驗證步驟

在這裡,我們將需要進行 分析對象 與 分析理由 的論述

分析對象

根據該原則的定義,我們分析的對象會是 software entities (classes, modules, functions, etc.),因此,請您要指出要分析的類別是哪一個
在軟體項目 E (可能是 類別、模組、函數)、開放 O 理由、封閉 C 理由

分析理由

請在這裡分別描述您認為存在著 開放 Open 與 封閉 Closed 的狀況
先描述為重構前的情況
在 軟體項目 E
開放 : 為什麼在軟體項目 E 中,為什麼不可以擴展,其 開放 O 理由
封閉 : 為什麼在軟體項目 E 中,為什麼不可以封閉修改,其 封閉 C 理由
經過重構之後,
軟體項目 E
開放 : 為什麼在軟體項目 E 中,為什麼可以擴展,其 開放 O 理由
封閉 : 為什麼在軟體項目 E 中,為什麼可以封閉修改,其 封閉 C 理由
請依序把上述的 E, O, C 替換您的分析結果

Liskov Substitution Principle LSP

在 Wiki 定義 Liskov Substitution Principle 如下
the Liskov substitution principle (LSP) is a particular definition of a subtyping relation, called (strong) behavioral subtyping, that was initially introduced by Barbara Liskov in a 1987 conference keynote address titled Data abstraction and hierarchy. It is a semantic rather than merely syntactic relation, because it intends to guarantee semantic interoperability of types in a hierarchy, object types in particular. Barbara Liskov and Jeannette Wing formulated the principle succinctly in a 1994 paper as follows:
LSP
在 Uncle Bob 的書中,對於 LSP ,定義 LSP 如下
Subtypes must be substitutable for their base types.
在最初的 LSP 提出者 Liskov ,定義 LSP 如下
if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program.
What is wanted here is something like the following substitution property: If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.

驗證步驟

在這裡,我們將需要進行 分析對象 與 分析理由 的論述

分析對象

根據該原則的定義,我們分析的對象會是 subtype 子型別 與 base type 基底型別 和 有用到基底型別的程式碼,因此,請您要指出這兩個型別與其相關程式碼的那些地方與行為,是否違反了 LSP 的要求
程式碼 P , 子型別 S , 基底型別 T

分析理由

請將您的分析結果,在這裡描述
符合 : 在程式碼 P ,對於 子型別 S ,是 可以替換 基底型別 T
違反 : 在程式碼 P ,對於 子型別 S ,無法 可以替換 基底型別 T,理由是 R
請依序把上述的 P, S, T, R 替換您的分析結果

Interface Segregation Principle ISP

在 Wiki 定義 Interface Segregation Principle 如下
no client should be forced to depend on methods it does not use.[1] ISP splits interfaces that are very large into smaller and more specific ones so that clients will only have to know about the methods that are of interest to them.
在 Uncle Bob 的書中,對於 ISP ,定義 ISP 如下
Classes whose interfaces are not cohesive have "fat" interface.
也可以說是
Clients should not be forced to depend upon interfaces that they do not use.
在 Uncle Bob 的網頁中,對於 ISP ,定義 ISP 如下
Make fine grained interfaces that are client specific.
在最初的 ISP 提出者 Robert C. Martin 的 Object Mentor SOLID Design Papers Series

驗證步驟

在這裡,我們將需要進行 分析對象 與 分析理由 的論述

分析對象

根據該原則的定義,我們分析的對象會 Client 用戶端,也就是要使用這個介面的用戶端與 Interface 介面,也就是抽象化的介面
用戶端 C , 介面 I , 方法 M

分析理由

請將您的分析結果,在這裡描述
對於 用戶端 C , 相依於介面 I ,將不會用到該 介面 I 的甚麼 方法 M
請依序把上述的 C , I, M 替換您的分析結果

Dependency Inversion Principle DIP

在 Wiki 定義 Dependency Inversion Principle 如下
refers to a specific form of decoupling software modules. When following this principle, the conventional dependency relationships established from high-level, policy-setting modules to low-level, dependency modules are reversed, thus rendering high-level modules independent of the low-level module implementation details.
在 Uncle Bob 的書中,對於 ISP ,定義 ISP 如下
High-level modules should not depend on low-level modules. Both should depend on abstractions.
Abstractions should not depend on details. Details should depend on abstractions.

驗證步驟

在這裡,我們將需要進行 分析對象 與 分析理由 的論述

分析對象

根據該原則的定義,我們分析出 高階模組 High-Level Module , 低階模組 Low-Level Module , 抽象 Abstraction , 細節 Detail
高階模組 H , 低階模組 L , 抽象 A , 細節 D

分析理由

請將您的分析結果,在這裡描述
對於 高階模組 H 原先有相依於 低階模組 L ,經過重構之後 高階模組 H 相依於 抽象 A
對於 抽象 A 原本相依於 細節 D ,經過重構之後, 細節 D 相依於 抽象 B
請依序把上述的 H , L , A , D , B 替換您的分析結果

關於 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 課程



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 課程