Monday, May 12, 2014

Abstract Factory, Ch9, Modern C++ Design

在 design pattern 裡面,abstract factory 跟 factory 不太好區分,我先間單介紹一下差別。首先,factory 的產品只有一個,所以只提供一個 B* create(),實際回傳的產品,可能是 B1*, B2*, 或者 B3*,但是 B1, B2, B3 都是繼承 B。相較之下,abstract factory 的產品是一組 A, B, C,所以也提供一組函式,

virtual A* createA();
virtual B* createB();
virtual C* createC();

因為 A, B, C 各自有自己的子類 A1/2/3, B1/2/3, C1/2/3 等,而且存在某條件限制 A1, B1, C1 一定要同時搭配使用,所以實作 Factory1 固定只會製造 A1, B1, C1,就可以在產生 Factory1 的同時保證「A1, B1, C1 一定搭配使用」。如果需要換成 A2, B2, C2 的組合,把 Factory1 換成 Factory2 就可以了。

也就是說,factory 跟 abstract factory 的產品不同,選擇要製造哪一種產品的時間也不同。Factory 是製造的產品的時候選擇,但是 abstract factory 在建立 factory 的時候,就已經決定要製造哪種產品。

這樣可以回答一個問題:能否把多個 factory 合併成一個 abstract factory?因為選擇產品的時間不同,所以本質就不同,沒辦法合併。考慮極端的情況,abstract factory 可能只有一種產品 A,也還是跟 factory 不同。另外,前一章的 template Factory 其實有個必須修改產品的缺點,雖然可以從外部改,但是每個產品都要改。這章的 template AbstractFactory 就完全不需要改產品,是不錯的設計。

這章使用的範例是遊戲裡面的敵人,分成 Soldier/Monster 不同類,遊戲本身又分成 easy/hard 兩種模式,所以在 Easy 模式下面,SillySoldier/SillyMonster 會一起出現。套用 abstract factory 的方式,可以在選擇難度的時候產生 EasyEnemyFactory,使得之後的遊戲一定會產生 SillySoldier/SillyMonster。

Summary

在 abstract factory 裡面,有兩個不同的 class,一個是 AbstractFactory 提供製造的界面,另一個是 ConcreteFactory 繼承 AbstractFactory 並提供製造的實作。因此必須定義這兩個 template class。AbstractFactory 的參數是產品的抽象型別,用 Typelist 輸入,就會產生所有 virtual Type* create<AbstractProduct>() 的界面。

typedef AbstractFactory<TYPELIST_2(Soldier, Monster)> AbstractEnemyFactory;
typedef ConcreteFactory<
    AbstractEnemyFactory,
    TYPELIST_2(SillySoldier, SillyMonster)>
    EasyEnemyFactory;
AbstractEnemyFactory* pFactory = new EasyEnemyFactory;
Soldier* pSoldier = pFactory->create<Soldier>();
pSoldier->speak();

至於 ConcreteFactory 的參數,就要給相對的 AbstractFactory 來繼承,並且給具體的產品型別,才能夠製造正確的產品。

實作的部份,AbstractFactory 使用 GenScatterHierarchy 多重繼承得到 create<A>(), create<B>() 等抽象界面,然後 ConcreteFactory 使用 GenLinearHierarchy 線性繼承來實作 create<A>(), create<B>() 這些具體實作。要特別注意,繼承的時候同名的 function 會有 hiding 的問題,但如果是 override 父類別的 virtual function 則沒有問題。

書中提到另外一種典型的 AbstractFactory 實作 PrototypeFactory。一樣是繼承 AbstractFactory 並且實作 create<T>,但是 Prototype 使用 clone() 來製造新的產品。既然是 clone() 就需要增加 member function setPrototype(),但是 C++ 繼承結構裡面有同名 member hiding 的問題,書中使用 friend global function 來避免。

更好的方法是用 cast 解決 hiding,這也是 C++ hiding 機制的標準解法。要取得 recursive template 的真正型別有點困難,需要 AbstractFactory 提供正確的 typedef。

public:
    // template wrapper function only instanciates in the most derived class
    template<class T>
    void setPrototype(T* pProto)
    {
        // calling hiding member function by the exact class name
        this->BaseCloneUnit<T>::Result::doSetPrototype(pProto);
    }
    // member function that is hiding by the most derived class
    void doSetPrototype(AbstractProduct* pProto)
    {
        m_prototype = pProto;
    }

protected:
    // this template depends on AbstractProductList and AbstractFactoryType
    // in AbstractFactory
    template<class T>
    struct BaseCloneUnit
    {
        typedef typename Tail<typename Reverse<
            typename Base::AbstractProductList>::Result, T>::Result TL;
        typedef CloneUnit<T, GenLinearHierarchy<
            typename TL::Tail,
            CloneUnit,
            typename Base::AbstractFactoryType> > Result;
    };

private:
    // member data that can be accessed in member function
    AbstractProduct* m_prototype;

使用結果如下,注意 setPrototype<Monster>() 要指定抽象產品,不然沒辦法轉出正確的型別。因為不是同一個 class overloading,所以必需要區分。

typedef AbstractFactory<TYPELIST_2(Soldier, Monster)> AbstractEnemyFactory;
typedef ConcreteFactory<AbstractEnemyFactory,
    TYPELIST_2(Soldier, Monster), CloneUnit> PrototypeEasyEnemyFactory;
PrototypeEasyEnemyFactory* pPrototypeFactory = new PrototypeEasyEnemyFactory;
AbstractEnemyFactory* pFactory = pPrototypeFactory;
pPrototypeFactory->setPrototype<Monster>(new SillyMonster);
pPrototypeFactory->setPrototype<Soldier>(new SillySoldier);
Soldier* pSoldier = pFactory->create<Soldier>();

狂想:如果用 C++11 variable template argument 或者 proprocessor macro 也許就不需要 Typelist + GenXXXHierarchy 這麼複雜的組合了。

No comments: