Dependency Injection 筆記 (4)

.NET 相依性注入》電子書內容連載 (4)


本文摘自電子書《.NET 相依性注入》,您可至書籍首頁下載試閱章節。
書籍首頁網址:https://leanpub.com/dinet

上集未完的相關設計模式...

Composite 模式


延續先前的電器比喻。現在,如果希望 UPS 不只接電腦,還要接電風扇、除濕機,可是 UPS 卻只有兩個電源輸出孔,怎麼辦?

我們可以買一條電源延長線,接在 UPS 上面。如此一來,電風扇、除濕機、和電腦便都可以同時插上延長線的插座了。這裡的電源延長線,即類似Composite Pattern(組合模式),因為電源延長線本身又可以再連接其他不同廠牌的延長線(這又是因為插座皆採用相同介面),如此不斷連接下去。

呃….延長線的比喻有個小問題:它在外觀上看起來也像是層層串接,容易和 Decorator 模式混淆。事實上,這兩種設計模式在結構上的確有相似之處。下圖所示為 Composite 模式的結構圖。


由此結構圖可以看得出來,Composite 模式 其實是個樹狀結構,呈現的是「整體-包含」(whole-part)的關係。樹上的每個節點(Leaf)都實作了相同的介面,而每個節點又可以包含多個子節點;就像檔案目錄結構那樣,每個資料夾底下都可以有零至多個資料夾。相較之下,Decorator 模式則是讓裝飾者看起來長得和被裝飾者一樣,但其實加上了額外的修飾。

Adapter 模式

當你的手機沒電,需要充電時,就算有電源延長線也沒用,因為手機充電時所需的電壓並不是一般家庭用電的 110 伏特交流電壓。此時我們通常會使用手機隨附的變壓器(adapter),將變壓器的電源插頭插在牆壁的電源插座,然後將變壓器的另一端連接至手機。像這樣把一種規格(介面)轉換成另一種規格的設計,就叫做Adapter Pattern(轉換器模式)。下圖所示為 Adapter 模式的結構。

仍使用先前 logging 範例來說明。假設我們沒有實作自己的 logging API,而是直接使用第三方元件。然而,考慮到將來很可能會改用另一套 logging 元件,於是決定使用 Adapter 模式來保護自己的程式碼。首先,必須先訂出 logging API 的介面,讓應用程式只針對此介面來寫入 log。此介面只定義了一個寫入日誌的方法,叫做 Log,參考以下程式片段。

public interface ILogger
{
    void Log(string msg);
}

接著設計 Adapter 類別。此類別須實作 ILogger 介面,並且在 Log 方法中轉而呼叫第三方元件的方法。程式碼如下:

public class CommonLogger : ILogger
{
    private ThirdPartyLogger logger = new ThirdPartyLogger();

    public void Log(string msg)
    {
        logger.WriteEntry(msg);  // 轉呼叫第三方元件的方法。
    }
}

如此一來,以後如果真的需要改用另一套 logging 元件,程式修改的範圍就只限定在 CommonLogger 類別而已。

Factory 模式

第一章曾經提過,每當我們在程式中使用 new 運算子來建立類別的執行個體,我們的程式碼就在編譯時期跟那個類別固定綁(繫結)在一起了。其實用 new 來建立物件還有個缺點:C# 的建構函式名稱就是類別名稱,不可任意命名;於是當類別有數個多載的(overloaded)建構函式時,光是閱讀傳入建構函式的參數列,有時不見得那麼容易明白程式的意圖。舉例來說:

var user1 = new User("Mike", 101, true);
var user2 = new User("Jane", 102, flase);

不如下列程式碼清楚:

var user1 = UserFactory.CreateAdministrator("Mike", 101);
var user2 = UserFactory.CreateDomainUser("Jane", 102);

其中的 UserFactory 就是擔任物件工廠的角色,它是個 static 類別,且唯一的任務就是生產特定類型的物件。程式碼如下所示。

public static class UserFactory
{
    public User CreateAdministrator(string name, int id)
    {
        // 略
    }

    public User CreateDomainUser(string name, int id)
    {
        // 略
    }
}

一般而言,Factory 模式泛指各種能夠生產物件的工廠,通常有三種模式:Factory Method(工廠方法)、Simple Factory(簡單工廠)、和 Abstract Factory(抽象工廠)。剛才的 UserFactory 就是一個 Simple Factory。
Note: 假設我們完全不知道 DI,或者覺得沒必要使用 DI,可是又希望程式碼不要和特定實作類別綁太緊(不想要直接 new 一個物件),此時 Factory 模式通常是個值得考慮的方案。
實作 Factory Method 模式時,通常會在一個基礎類別中定義建立物件的抽象方法(難怪叫做「工廠方法」),然後由各個子類別來實作該方法。若將先前的 UserFactory 改成以 Factory Method 來實作,其類別結構如下圖所示。

Abstract Factory 比前面兩種工廠模式要稍微複雜一些,它是用來建立多族系的相關或相依物件,且無須指名物件的具象類別。實作此模式時,會將一組建立物件的方法定義成一個介面,代表抽象工廠。然後,你可以撰寫多個類別來實作該介面,而這些類別的角色就像真實世界中的工廠,類別中的每一個工廠方法則有點像是真實工廠裡的一條生產線。當用戶端需要建立該族系的物件時,就是利用其中一種具象工廠(concrete factory)來生成物件。此外,由於具象工廠都實作了同一組介面,所以用戶端甚至可以在執行時期動態切換成不同的工廠,以建立一組相關的物件。
如果你跟我一樣,常常搞混這三種 Factory 模式,我發現《Refactoring to Patterns》這本書的 6.2 節裡面有一張簡略的結構圖挺有用。我依樣畫了一張,如下圖所示,其中的粗黑線代表建立物件的函式。下次忘記時,不妨回來瞄一眼底下這張圖,也許能幫你回想起來它們之間的差異。


OK! 設計模式的部分就概略介紹到此,後續章節中如碰到其他模式,也會一併介紹(例如 Strategy、Repository、Service Locator 等等)。

「我曾在這樣的十字路口:努力學習各種模式,希望成為一個更好的軟體設計師;但現在,為了真正成為更優秀的軟體設計師,我必須降低對模式的依賴。」
—— Joshua Kerievsky. 《Refactoring to Patterns》 作者

未完待續....

Post Comments

技術提供:Blogger.