class Fruit{};
class Apple : public Fruit{};
class Orange : public Fruit{};
class Grape : public Fruit{};
class FruitFactory
{
public:
enum EN_FRUIT_TYPE
{
E_APPLE,
E_ORANGE,
E_GRAPE,
};
static FruitFactory* getInstance(){...}
Fruit* create(EN_FRUIT_TYPE enType)
{
switch(enType)
{
case E_APPLE:
return new Apple();
case E_ORANGE:
return new Orange();
case E_GRAPE:
return new Grape();
}
}
};
...
Fruit* foo = FruitFactory::getInstance()->create(FruitFactory::E_APPLE);
上面的例子裡,最明顯的特徵就是有個 create(),根據參數透過 switch 來決定要產生哪個實體。我個人每次寫這段的時候,都要檢查再三,確定 E_APPLE 真的是對應 new Apple(),因為每個 case 都很類似,所以複製貼上再修改,很怕眼花改漏改錯。我想,很多書上不建議用 switch 這是其中一個理由。
像這樣重複的 code flow 的確是 template 的好戲。這章的作法,是把 Factory 寫成 class template,並且把 switch-case 用一個 map 取代。如此一來,需要 Factory class 的時候,只要套用 template 就可。要注意,map 跟 switch 的本質不同,map 是個資料結構,是一個變數,要像switch 一樣到處都可以呼叫,就要是 global 的。也就是說,這裡假設 Factory class 一定是 singleton 或者 mono-state。像上面的 FruitFactory::create() 沒有使用 local 變數,很容易滿足這個條件,這也是最常見的情況。
template<
class ProductType,
typename IdType,
typename Creator = Fruit* (*)()>
class Factory
{
public:
static Factory* getInstance()
{
static Factory s_instance;
return &s_instance;
}
bool registerType(IdType id, Creator creator)
{
m_map[id] = creator;
return true;
}
ProductType* create(IdType id)
{
if(m_map.end() == m_map.find(id))
{
return NULL;
}
return m_map[id]();
}
private:
std::map<IdType, Creator> m_map;
};
...
typedef Factory<Fruit, int> FruitFactory2;
Factory template 必要的參數是 ProductType,然後使用之前要先把 id-creator 的對應關係註冊,而且要在整個程式最開始就註冊,所以要用 static 的方式呼叫。下面是每個要支援的 type 都要註冊。
class Apple : public Fruit
{
static bool m_bRegistered;
public:
enum
{
ID = 1
};
static Fruit* create()
{
return new Apple();
}
};
...
bool Apple::m_bRegistered = FruitFactory2::getInstance()->registerType(Apple::ID, Apple::create);
最後,使用方式跟原本一樣,呼叫 creator 並且指定 id 就可以了。
Fruit* bar = FruitFactory2::getInstance()->create(Apple::ID);
用 Factory template 主要的好處有兩個。第一,每次用到 Factory,只要 typedef 就可以產生一個新的 Factory。第二,同一個 Factory,要加入新的 type 的時候,不必改 Factory 本身,用 add 取代 modify,不會影響舊的功能。
幾個細節問題討論如下。
- ID 如何決定則是一個比較傷腦筋的問題,因為 C++ 沒有提供固定的 type id,所以一般是用 hard code 使用字串或者亂數作為索引。甚至,用古老的方式寫 enum 集中管理也是個方式。
- 錯誤處理的部份,可能發生 map 找不到的問題,也就是 switch 裡面的 default case,用 policy class 可以選擇不同處理方式。
- 最後一個觀察,是 singleton 的部份。前面提到 map 只能有一份,而且 id-creator 必須在程式一開始註冊,所以使用 singleton 是很合理的。這章直接套用 Singleton template 來完成,可以在不同地方提供 Factory 不同的 Singleton 實作,非常的有彈性。
No comments:
Post a Comment