Design Pattern 設計模式 Abstract Factory, Factory Method, Singleton, Builder, Prototype
Design Pattern 軟體工程與哲學

Design Pattern 設計模式 (1): Abstract Factory, Factory Method, Singleton, Builder, Prototype

商業,創業,美食,葡萄酒,閱讀,網路科技。

這是我的 FB粉專 以及 IG,我比較常使用 Threads,歡迎大家追蹤互動~

本系列目標在於依照 [1] 這本經典所介紹的 Design Patterns 做一個 review 與整理。

本文我想先討論 [1] chapter 1 中 code reuse 的概念,接下來整理 Abstract Factory,Factory Method,Singleton,Builder,Prototype patterns。

在 [1] chapter 1 中 code reuse 主要分為三大類:Inheritance, Composition, Parameterized Types (Templates)。其中各自的優缺點是:

1. Inheritance

:繼承關係程式碼可讀性高,架構容易理解。
缺:繼承關係是在 compile-time 就決定,flexibility 低。
缺:sub-class 有權更動 super-class 內部資料與行為,打破封裝。反之,super-class 的更動也會影響 sub-class 的行為。

2. Composition

:可以在 run-time 再決定 composition objects,flexibility 高。
:維持 composition objects 的封裝性。
缺:程式可讀性低。

3. Templates

compile-time 定義。

書中的意見是:Composition 是比 Inheritance 好的 reuse 技術,Templates 則提供了第三種選項。我個人認為:Inheritance 與 Composition 使用時機上並不同,有需要更動到 class 內部的要選用 Inheritance,無此需求的時候,當然就是選擇 Composition 了。

接著開始 5 個 design patterns 的討論。

1. Abstract Factory

首先以範例先描述 Abstract Factory。假設麥當勞的一號餐與二號餐會隨著夏季冬季作口味上的調整,則其模型可以如下:

Design Pattern 設計模式 Abstract Factory

以上例子可以勾勒出 Abstract Factory 的使用時機及好處:

  • 生產 product (一號餐二號餐) 的責任完全與系統無關,系統甚至不須知道目前是夏季一號餐還是冬季一號餐,系統只要照著一般一號餐的管理與使用方式即可。以程式語言來說,系統與 factory 和 product 實作完全沒相依性,是很好的設計。
  • 系統中所需物件可以被分類。以此範例來說,我們總共有夏季一號餐、冬季一號餐、夏季二號餐、冬季二號餐。很直覺的想法就是把他們分為夏季一組、冬季一組。當系統 configure 成夏季時即產生夏季的餐點,當系統 configure 成冬季時即產生冬季的餐點。

Abstract Factory 實作須知:

  • concrete factory 應以 singleton 方式實作。
  • Abstract Factory 新增 product 可以改為用參數指定的方式。以上面範例來說,假如要新增夏季三號餐,當然夏季麥當勞廚房要改變,另外麥當勞廚房冬季麥當勞廚房也要一起變動。所以另一種方式是,兩個 method 生產一號餐生產二號餐改為生產餐點(餐點號碼),這樣對單一 concrete factory 的擴充性會方便許多。

2. Factory Method

首先以範例先描述 Factory Method。

Design Pattern 設計模式 Factory Method

顧名思義,Factory Method 著重在 creation function 這個 method。Super-class 有一個 creation function,依照需求利用 sub-class 對此 creation function 做轉型。在未來開發而言,Factory Method 支援 parallel class hierarchies,這是很值得參考的做法。

Factory Method 實作須知:

  • 同 Abstract Factory 第二項實作須知,參數化的 creation function 會有彈性很多。
  • 使用 Factory Method 會容易產生過多的 sub-class (因為基本上它是與 product type 綁在一起)。此時 sub-class 可以以 template 的方式實作,理論上就會只有一個 template sub-class 了。
  • [1]中提到 naming conventions。很多 framework 甚至規定 Factory Method 的 function name,使開發者一看就知道是 Factory Method。很多人可能認為這是小東西,但我個人認為軟體開發程式的基礎建設一定要做好,將來才可以是可管理、可測試的專案。

3. Singleton

Singleton 的範例我想直接用 sample code 來描述。

class Singleton
{
public:
    static Singleton* getInstance( );
protected:
    Singleton( );
private:
    static Singleton* m_instance;
};

Singleton::m_instance = NULL;

Singleton* Singleton::getInstance( )
{
    if( m_instance == NULL )
    {
        m_instance  = new Singleton;
    }
    return m_instance;
}

Singleton 的觀念有兩點要注意的是

  1. Singleton 的作法其實就是 global variable 的改良。
  2. Singleton 中其實可以放多個 instance. 此時 Singleton 就會多一個管理器的角色。

Singleton 實作上還有很多變化,上述只是基本精神,實際應用上應隨著系統需求而靈活變化才是。

4. Builder

包含一個 director 和一個 builder。client 端擁有一個 director,builder 或以 reference 的形式存在在 director 端裡 (在 director construct 時),或是當參數傳入。

底下是 client 端的 sample code

// ...
MyDirector1 myDirector1;
MyWorldBuilder builder;
myDirector1.createWorld(builder);
MyWorld world = builder.getWorld( );
// ...

MyWorldBuilder 可能有 buildTrees, buildHouses, buildRivers 等 method。以上例來說,MyDirector1 生成了一種世界,MyDirector2 則會生成另一種世界,with the same MyWorldBuilder

使用時機方面,當最終需求的 product 有如樂高一般可以由不同的 component 所組成時,可以使用此 pattern。director 的 creation function 可以選擇 create product part A and B, 或是 B and C, 或是其他組合。

5. Prototype

Prototype pattern 最重要基本的精神就是使用 Clone method 把自己拷貝一份給 client。Client 需要 create prototype product 時,收到一個 prototype base class 的 pointer,調用 Clone method。

同樣是 run-time 決定要生成哪一個 concrete product 的 design pattern,Prototype pattern 與吃參數的 factory method 差別在哪?

以吃參數的 factory method 來說,client 會知道要生成哪一個 concrete product。因為通常 factory 會存在在 client 中,所以 factory method 的參數會經過 client。另外,client 也許也要確認 factory 是否支援此參數。

Prototype pattern 來說,client 完全不知到目前要生成哪一個 concrete product,也沒有要確認任何 concrete product 相關正確性的責任。

結論:Prototype pattern 會簡化系統 (client) 的設計與工作。但工作不會憑空消失,Prototype pattern 把相關的工作移到了類似 prototype manager 的模組去做。

在一般情形中,我個人還是會使用吃參數的 factory method。但在生成物件極度動態的情形下 (Ex: 依照 config 不同而不同,依照 branch 不同而不同),則會使用 Prototype pattern。

最後實作時要注意的一點是,C++ copy constructor 是 member-wise copy。但是 Clone method 所需的是 deep copy,實作時要稍微注意一下。

[1] “Design Patterns”, Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides.

商業,創業,美食,葡萄酒,閱讀,網路科技。

這是我的 FB粉專 以及 IG,我比較常使用 Threads,歡迎大家追蹤互動~