2018年7月12日 星期四

一個違反 LSP Liskov Substitution Principle 里氏替換原則 的範例說明

有很多人對於 LSP Liskov Substitution Principle 里氏替換原則 所講述的內容,看得很模糊,有些時候是學習者對於物件導向程式設計觀念不慎清楚、開發經歷中,也甚少經常設計類別繼承的程式碼,不過,也有人說,我都有設計類別繼承,要是我,才不會有這樣的問題;可是,這是個原則,講的是當您遵從這個原則之後,就不會設計出有行為異常的子類別,有些時候,您也可能需要維護別人開發的舊專案,這個專案也許不知道經過幾個人來維護過了,往往有需求變更的時候,為了客戶時效要求,手頭上有很多專案要開發,就直接繼承類別,產生新的子類別來設計出滿足變更需求的程式,當然,經過一段時間之後,這個專案上就會產生違反 LSP 的問題;不過,說實在的,若這個專案從頭到尾都是您自己開發,也是有可能產生違反 LSP 的問題。一旦程式碼出現違反 LSP 的原則,就會產生出許多意想不到的問題,而這些問題,很多時候是在設計與除錯時候,無法立即發現到的 (更多關於 LSP 的介紹,網路上已經有海量的文章,請各位自行去搜尋、研究)
現在,讓我們來看看底下的範例,您能夠看出它存在著甚麼問題嗎?
另外,他是否有遵從 LSP 原則嗎?
不論答案是肯定或者是否定,請您要提出的論述觀點,這樣,才能夠知道,您是否真正明瞭甚麼是 LSP Liskov Substitution Principle 里氏替換原則
C Sharp / C#
class 鳥
{
    public virtual void 飛() { Console.WriteLine("鳥在飛"); }
    public virtual void 吃() { Console.WriteLine("鳥在吃"); }
}
class 老鷹 : 鳥
{
    public override void 飛() { Console.WriteLine("老鷹在飛"); }
    public override void 吃() { Console.WriteLine("老鷹在吃"); }
}
class 鴕鳥 : 鳥
{
    public override void 飛() { throw new NotSupportedException("鴕鳥不能飛"); }
    public override void 吃() { Console.WriteLine("老鷹在吃"); }
}
底下是使用上面類別的範例程式碼
C Sharp / C#
class Program
{
    static void Main(string[] args)
    {
        List<鳥> birds = new List<鳥>();
        birds.Add(new 鳥());
        birds.Add(new 老鷹());
        birds.Add(new 鴕鳥());
        BirdsFly(birds);
    }
    static void BirdsFly(List<鳥> birdList)
    {
        foreach (var item in birdList)
        {
            item.飛();
        }
    }
}

關於 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月11日 星期三

對於學習 SOLID 五個原則 Principle 的感想

因為工作上的需要,因此,我特別安排時間來進行 SOLID OOD 物件導向程式設計的研究與學習,我想,我應該是與大家一樣,開始都是從 Google 上來搜尋 SOLID SRP OCP LSP ISP DIP Principle 等關鍵字,看看網路上的人怎麼說,也須我的資質不好,搜尋到一大票的文章,怎麼看,怎麼不懂,最重要的是,當我好不容易看懂,例如 Single Responsibility Principle SRP ,接著看到更多的文章所說明的內容、圖片、解釋、範例,就更加的模糊與不清楚他到底是要做甚麼用到、可以幫助我做甚麼事情、我該如何將我的程式碼撰寫或重構成具有 SRP 精神的程式碼,這也包括了不同作者所舉例的程式碼,並不是我孰悉的 C#,這讓我在閱讀那些程式碼的過程中,也是備感艱辛;過程中,我也產生了許多的疑問,再次以 SRP 為例,為什麼每個文章作者,對於所舉例的範例程式碼,緊接著就說這段程式碼具有三個責任,所以,我們要把這三個責任進行分離開,我的老天鵝呀,我是真的不知道為什麼是三個責任,不是兩個,也不是五個。
好吧,既然廣泛涉獵的許多網路上的 SOLID 的文章,那麼,只好痛下決心,花點索費,到 Amazon 去買 Uncle Bob 寫的兩本書來看看 (會去買 Uncle Bob 的書,這是因為 SOLID 是 Uncle Bob 根據不同人提出的分享,綜合出這五個原則) ,也花了點時間,找出 Uncle Bob 當初所提出的 SOLID 五篇文章,既然東西都到手之,就要開始花點時間把這些最初的資料給他閱讀一番。不過,最後,我還是想辦法把當初提出這些原則人寫的文章、書,也給他購買或者找出來,並且從中細細體味出箇中奧妙之所在。
因此,對於想要把 SOLID 弄清楚的人,在此提出幾個建議

OOP 物件導向程式設計

若您對於 OOP 的四個特性不慎了解,甚至沒有研究過 OOP 四個特性,我覺得您應該要先去了解與孰悉 OOP 的四個特性:抽象、封裝、繼承、多型

弄清楚這些名詞的意義

當您在進行 SOLID 學習過程中,需要處處存著兒時好奇心態,保持您的疑問,雖然,當時您可能無法理解或明瞭其中含意,不過,這對於您日後學習上,會有著明顯的好處;若平常都有做善事,有著好機緣,碰到真正會的有緣人(千萬不要去問自以為是,處處都是我認為、我想的、我平常都是這樣做的的人去問,這樣,您會走火入魔的),把你蒐集起來的問題,向他們請教看看。
首先,您需要了解 甚麼事 Principle 原則,因為, SOLID 是由五個 Principle 所組成,若您不瞭解何謂 Principle 的意義,那麼,就算您學會了這五個 Principle,您覺得對你會有甚麼幫助呢?
緊接著,要把每個原則的每個英文字與其解釋(我是參考 Uncle Bob書中對於這五個原則的描述),挑出主要的名詞,了解他們存在的意義與目的 (千萬不要使用您本身所學的經歷來看待這些名詞,畢竟,這些原則是早在 20 年前所提出來的,那個時候,可是沒有 C# / Java 這樣的語言存在,還是那句話,不要憑感覺來認為自己就是一切),這樣會有助於您去閱讀書中、文章中之各原則的論述。

SRP Single Responsibility Principle 單一責任原則

A class should have only one reason to change
請了解甚麼事個 Responsibility 責任,不過,在其他文章中,您可能會看到有些內容會使用 Module 模組來取代 Class 類別,您可以嘗試體會與思考看看,為什麼會有 Module 這個名詞出現,這會對於您之後學習過程中有莫大的幫助;另外,也需要甚麼是 Change 變更需求,為什麼會有它,他會造成甚麼影響,如何改善因為 Change 所產生出來的問題。總之,凡事抱著疑問來學習。

OCP Open Closed Principle 開放封閉原則

Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification
OCP 開放封閉原則,是我們進行專案開發過程中,最為重要與經常會面對的問題,所以,務必要花些時間來精通才好。
在這裡,請要了解這些詞彙的意義與精神: Open / Closed / Software Entity / Extension / Modification 。對於後面兩者,請要去了解,就是目標對象指的是誰,也就是誰要開放,誰要封閉,究竟怎麼開放,怎麼封閉。
在您觀看 OCP 內容的時候,會看到很多文章舉的例子,都是使用 Interface 介面,來做為重構成為符合 OCP 的作法,請千萬不要畫地自限,以為,想要符合 OCP 的精神,就只能使用 介面 來解決,有興趣的人,可以去嘗試看看,要做出符合 OCP 精神的程式碼,至少有六七種以上的技術可以做到。

LSP Liskov Substitution Principle 里氏替換原則

Subtypes must be substitutable for their base types
這個原則主要的重點就是替換這個名詞,您也許會覺得很奇怪,這到底是甚麼原則,為什麼要放在 SOLID 這五個原則內,他有甚麼重要性?在想要了解這個原則,您應該要先從他的背景來研究,那就是 OOP 裡面的繼承與多型,也就是說,若您對於這兩個名詞也一知半解,那麼,這個原則所提到的內容,您是無法體會出來的。

ISP Interface Segregation Principle 介面隔離原則

Clients should not be forced to depend upon interfaces that they don't use
這也是個大家覺得好莫名的原則,介面與隔離,是這個原則要探討的重點,還是那句話,若您在開發程式設計過程中,幾乎沒有自己設計過介面,或者不太了解介面的應用與特色,那麼,您會學得很辛苦;另外,就是一個問題,為什麼會產生一個具有那麼多方法宣告的介面,也許,您會說:要是我自己開發,就不會有這樣的問題?可是,請不要用自己的經歷、角度來看這些原則,在一個專案開發與設計過程中,往往會有多人一起進行開發,當然,任何人都可以去修改介面的宣告,所以,您應該已經知道為什麼會產生一個超級胖的介面了。因此,了解這樣的介面會產生的副作用,並且規避這樣的現象產生,也就是這個原則的精神了。

DIP Dependency Inversion Principle 相依反轉原則

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
這個原則需要用到兩句話來說明,因此,我們需要了解這兩句話中的各個腳色與扮演目的:高階模組、低階模組、模組、抽象、明細、相依。喔喔,這五個原則中,看樣子是這個原則中出現了最多的名詞,我個人覺得最重要的詞彙就是 相依 Dependency ,了解何謂相依、為什麼會相依、相依會造成甚麼樣的問題、怎樣才是一個好的相依,這樣,也許您就已經對於這個原則了解一半了。不過,若您的程式設計與開發生涯中,幾乎沒有用到過或者很少用到過相依性注入這樣的設計模式,那麼,您可能無法深刻體會出這個原則想要幫您解決甚麼問題。

打通任都二脈

若您經過研究,有了解與孰悉 OOP,對於這五個原則的各個用到的詞彙也明瞭,甚至有看過這五個原則當初的背景文章,而且也看過各種原則的範例程式碼,那麼,您已經完成了第一步,接下來,您將會需要
  • 找出與練習撰寫出 違反原則的程式碼,自己嘗試重構這個程式碼,使其符合原則
  • 嘗試自行分析,若一個程式碼,符合 OCP,是否就符合 LSP,反過來說,符合 LSP ,是否就是符合 OCP;另外一個是,若程式碼符合 OCP,是否就符合 DIP,反過來說,符合 DIP ,是否就是符合 OCP,嘗試挑戰看看這樣的論述是否都是為真,還是不是,若不是,您可以舉出這樣的例子呢?

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