有趣的是,在比較新的語言有個現像,如 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 有好處有壞處,我個人一直都蠻愛用的就是了。
沒有留言:
張貼留言