2013年9月29日 星期日

Abstract Factory

Abstract Factory 譯作抽像工廠,其實這真的很難顧名思義,不過大致上可以由名字看得出來,這個模式的目的在於生產出抽像類別,而抽像類別有個無法被建構的特性,抽像類別的目的是用來描述屬於該種類的東西具備著什麼樣的型像與功能。

基於抽像類別的特性可以知道 Abstract Factory 只描述著會生產出某種東西,實際生產出來的物件可以隨著不同的條件下改變,使用 Abstract Factory 的人只要知道要某類的東西時找它要就對了,其餘不用管,因為這個特性,Abstract Factory 非常適合拿來解決以下問題

1. 經常變動的設計 (例如零件成本計算公式)
2. 充斥各處的 if elseif 或 switch case (這不用解釋吧)
3. 依用戶設定而改變行為的功能 (例如應用程式外觀樣板與配色)
4. 因環境而改變行為的功能 (例如不同國家文化所造成的差異)

UML 的圖與基本的說明其實太多地方都有了,這裡就簡單的寫一個 C++ 的例子來做個筆記,假設我寫了一個計帳軟體,該軟體有提供日曆查詢的功能及專門用來算帳的計算機,此時勢必要自己做一個日曆和計算機,但日曆和計算機這種東西其實是會根據用戶所在國家而有不同的外觀,例如計算機方面,小數點和千分位在德國就和美國的長像是完全反過來的,日曆方面,各國家的國定假日也是不同的,如果我們不使用 Abstract Factory 來處理,就得在專案中到處充斥著 if ( 國別 == 台灣 ) elseif ( 國別 == 德國 ) 這類判斷式,所以我們先將這類會因環境而改變行為的元件集中做成一個名為 CulturalFactory 的類別

class CulturalFactory
{
    public:
        CulturalFactory(void);
        ~CulturalFactory(void);
        // Creater
        virtual Calendar* CreateCalendar() { return new Calendar; };
        virtual Calculator* CreateCalculator() { return new Calculator; };
        virtual CString GetName() { return _T("CulturalFactory"); };
};

class Calendar
{
};

class Calculator
{
};

接著我要再依各種文化為群組,產生多群繼承自 CulturalFactory、Calendar、Calculator 的類別群,例如

// Japan
class JapanCulturalFactory : public CulturalFactory
{
    public:
        JapanCulturalFactory(void);
        ~JapanCulturalFactory(void);
        // Creater
        virtual Calendar* CreateCalendar() { return new JapanCalendar; };
        virtual Calculator* CreateCalculator() { return new JapanCalculator; };
        virtual CString GetName() { return _T("JapanCulturalFactory"); };
};

class JapanCalendar : public Calendar
{
};

class JapanCalculator : public Calculator
{
};

// Taiwan
class TaiwanCulturalFactory : public CulturalFactory
{
    public:
        TaiwanCulturalFactory(void);
        ~TaiwanCulturalFactory(void);
        // Creater
        virtual Calendar* CreateCalendar() { return new TaiwanCalendar; };
        virtual Calculator* CreateCalculator() { return new TaiwanCalculator; };
        virtual CString GetName() { return _T("TaiwanCulturalFactory"); };
};

class TaiwanCalendar : public Calendar
{
};

class TaiwanCalculator : public Calculator
{
};

// Europe
class EuropeCulturalFactory : public CulturalFactory
{
    public:
        EuropeCulturalFactory(void);
        ~EuropeCulturalFactory(void);
        // Creater
        virtual Calendar* CreateCalendar() const { return new EuropeCalendar; };
        virtual Calculator* CreateCalculator() const { return new EuropeCalculator; };
        virtual CString GetName() { return _T("EuropeCulturalFactory"); };
};

class EuropeCalendar : public Calendar
{
};

class EuropeCalculator : public Calculator
{
};

// America
class AmericaCulturalFactory : public CulturalFactory
{
    public:
        AmericaCulturalFactory(void);
        ~AmericaCulturalFactory(void);
        // Creater
        virtual Calendar* CreateCalendar() { return new AmericaCalendar; };
        virtual Calculator* CreateCalculator() { return new AmericaCalculator; };
        virtual CString GetName() { return _T("AmericaCulturalFactory"); };
};

class AmericaCalendar : public Calendar
{
};

class AmericaCalculator : public Calculator
{
};

可以看到很可惜的,使用 Abstract Factory 並沒有辦法讓我們少打一些程式碼,反而要多建立很多的類別,但是當我們要建構與使用這類型的類別時,使用 Abstract Factory 立刻可以讓我們感覺到乾淨簡潔的程式邏輯,判斷式幾乎只會出現在我們的應用程式被開啟的當下,即根據作業系統查詢到的國別做初始化 Abstract Factory 的動作

CulturalFactory* tools;

if ( 國別 == 日本 )
{
    CulturalFactory* tools = new JapanCulturalFactory;
}
else if( 國別 == 歐洲某國 )
{
    CulturalFactory* tools = new EuropeCulturalFactory;
}
else if ( 國別 == 美國 )
{
    CulturalFactory* tools = new AmericaCulturalFactory;
}
else
{
    CulturalFactory* tools = new TaiwanCulturalFactory;
}

在初始化之後,我們不再需要撰寫這些判斷式了,需要工具時,直接找 tools 這個物件拿就行,它自然會交給我們適合的實體物件,例如

Calendar *calendar = tools->CreateCalendar();
Calculator *calculator = tools->CreateCalculator();

使用 Abstract Factory 還有一個很棒的優點,因為應用程式的商業邏輯都是操作著抽像類別,而這背後對應著一個特別為某種狀況所撰寫的類別群,如果我們哪天要更換掉這個類別群時,例如要改寫美國文化的日曆與計算機,我們不需要重新測試應用程式的商業邏輯層,因為我們不會改到那一層,換言之我們只要對新的美國的類別群做 Unit Test 即可保障程式流程無誤。

沒有留言:

張貼留言