2013年9月30日 星期一

Singleton 搭配 Abstract Factory

自從多年前學會 Singleton 這種實作技巧以後,只要是非拋棄式的專案我幾乎都會用到且用途都一樣,就是取代全域的物件,一般書籍介紹到 Singleton 時也是提到這是用來確定某一個物件的唯一性所用的技巧。

有趣的是,在比較新的語言有個現像,如 C# 已經沒辦法宣告單純的全域變數了,只能利用在 class 裡面放 static member variable 的方式來操作類似全域的物件,想在 C# 專案中做出如 C++ 的 define 也是得用 class 包含 const 成員的方式,所以一般提倡不要用全域物件的說法,在 C# 等語言中根本不會因為如此發生意外,我對於 Singleton 的迷信似乎也愈來愈薄弱。

除此之外還有另一種特殊狀況,例如 MFC 的專案,我個人反而會刻意的使用全域的變數來存放一些數據,因為在建立一個 MFC 的新專案時,編輯器會自動幫我們建立一個叫作 theApp 的全域物件,這個 theApp 的型態就是該應用程式,以一個名為 MyApp 的 Windows 專案為例,因為所有新建立的 MFC 類別在 header 檔案都有 incldue MyApp.h

所以在 MyApp.cpp 中有這麼一行 CMyApp theApp;

而在 MyApp.h 中有這麼一行 extern CMyApp theApp;

意即所有 include MyApp.h 的類別都可以直接使用 theApp 來操作所有在 CMyApp 實體中的成員,當然最大的壞處就是我可以在任一個類別中宣告一個成員為 CMyAppApp theApp 來讓這個類別用錯物件,更別提若宣告的是 int theApp 這種編譯器許可的同名不同型別狀況,這當然都是一般書籍提到全域變數時會舉例的壞處,但這個 theApp 是 VisualStudio 幫我們自動生出來的,所以我為了讓這個專案看起來更有 MFC 的味道而刻意的使用它,即便會被 Singleton 的信眾嘲笑也要用。

之前有寫一篇文章分享過 Abstract Factory 這個模式,我個人覺得這個模式最正宗的用法是把它用在一個 "生命週期幾乎 == 應用程式開啟~關閉時間" 的物件,如此才能讓 Factory 被初始化的入口只有一個,達到真正簡化商業流程程式碼撰寫複雜度的目的,而 Singleton 就是一個很好的方法,但 Singleton + Factory 如何在初始化時建構不同實作細節的實體呢?我個人偏好用列舉成員的方式來解決,反正我們本來就要在抽像類別中 include 或 import、using namespace 各實作類別,並沒有什麼要多做的麻煩事,我同樣利用先前的 Abstract Factory 中所舉的例子來做示範,程式碼大致相同,差別在於抽像類別 CulturalFactory 中多了 GetInstance 的靜態方法

class CulturalFactory
{
    public:
        // Factory Instance
        typedef enum {CF_Taiwan, CF_Japan, CF_Europe, CF_America} CFType;
        static CFType FacoryType;
        static CulturalFactory* Instance();

        // Creater
        virtual Calendar* CreateCalendar() { return new Calendar; };
        virtual Calculator* CreateCalculator() { return new Calculator; };
        virtual CString GetName() { return _T("CulturalFactory"); };
    protected:
        CulturalFactory(void);
        ~CulturalFactory(void);
    private:
        static CulturalFactory* _instance;
};

CulturalFactory* CulturalFactory::Instance()
{
    if(_instance == NULL)
    {
        if(CF_Japan == FacoryType)
        {
            _instance = new JapanCulturalFactory;
        }
        else if(CF_Europe == FacoryType)
        {
            _instance = new EuropeCulturalFactory;
        }
        else if(CF_America == FacoryType)
        {
            _instance = new AmericaCulturalFactory;
        }
        else
        {
            _instance = new TaiwanCulturalFactory;
        }
    }
    return _instance;
}

然後在 Client 的應用程式初始流程中,取得作業系統的 Cultural Value 後如此初始化 CulturalFactory

CulturalFactory::FacoryType = CulturalFactory::CF_Europe;

需要用到日曆與計算機時的寫法改為這樣子

Calendar *calendar = CulturalFactory::Instance()->CreateCalendar();
Calculator *calculator = CulturalFactory::Instance()->CreateCalculator();

先前有說過利用 Abstract Factory 是很有利於測試的寫法,我們只要對新的類別群做 Unit Test 即可,但是在和 Singleton 扯上關係後就沒那麼單純了,Singleton 最被人垢病的就是它不易測試的特性,不論是用來取代全域變數還是和 Abstract Factory 做搭配,主要原因在於 Singleton 的生命週期太長了,且因為靜態成員的特性而無法重新對他做初始化,所以測試的函蓋率會有問題,以上面的例子來說明,假設我第一次測 Taiwan 的流程正確且結束後要測 Japan 的流程,此時 Singleton 已經生成為 Taiwan 了,總之 Singleton 有好處有壞處,我個人一直都蠻愛用的就是了。

沒有留言:

張貼留言