code style

4/13/2007

Factory Method

Objective

定義生成物件的介面,但是讓子類別決定該具現哪個類別的物件。工廠方法將類別具現化交給子類別去處理。

Motivation

level designer利用level editor編輯了一個精心製作的關卡,其中有各式各樣的怪物配置在各處。level editor會將這些關卡資訊輸出成一個特定格式的檔案。在遊戲中,programmer將會讀取這些資訊並生成出正確的怪物類別。

由於該具現哪一類別的怪物將會隨著關卡設定而異,所以程式無法事先預測生成哪些怪物類別--程式只知道何時該生成怪物,而不知道該具現那一類別的怪物。解決方法就是使用Factory Method「將類別具現化交給子類別去處理」。



Implementation

class Creator {
public:
virtual Product* Create(int type);
};

Product* Creator::Create(int type) {
switch(type) {
case Type1: return new MyProduct1;
case Type2: return new MyProduct2;
case Type3: return new MyProduct3;
//...
case TypeN: return new MyProductN;
}
return NULL;
}


Problem

上述 pseudo code 雖然可以正常運作,但是卻非常難以擴展。譬如,我們需要在遊戲中加入新的怪物類別,所以不得不修改Creator::Create()、添加相對應的 case、原始碼中也需要include該怪物類別的header file。要怎麼修改才可以讓Factory Method任意添加新的類別而不用更動原本的原始碼呢?


Solution

透過template,我們可以自動產生每一個product的creator。然後再利用register的方式將creator註冊至factory method。那麼factroy method便可以找出對應的creator來具現所需要的類別。


class Factory {
public:
Object* Create(int type) { return mCreatorMap[type]->Create(); }
void Register(int type, const Creator* pCreator) { mCreatorMap[type] = pCreator; }
static Factory* GetInstance() { return mpInstance; }
private:
Factory() {}
typedef std::map CreatorMap;
static CreatorMap mCreatorMap;
static Factory *mpInstance;
};

Factory* Factory::mpInstance = new Factory;
Factory::CreatorMap Factory::mCreatorMap;


為了讓creator方便地註冊至factory method。我們可將factory method實做成Singleton模式。結合Singleton模式,我們可以利用靜態變數與物件建構式,將creator在程式初始化時便自動地註冊至factory method。


class Creator {
public:
virtual ~Creator() {}
virtual Object* Create() const = 0;
};

template
class ConcreteCreator : public Creator {
public:
ConcreteCreator() { Factory::GetInstance()->Register(T::_Type(), this); }
Object* Create() const { return new T; }
private:
static ConcreteCreator mRegister;
};


當我們需要添加一個新的怪物類別時,我們只需要實做出新的類別,並且加入下列代碼:

ConcreteCreator ConcreteCreator::mRegister;

That's all. :)

4/04/2007

Singleton

Objective

確保類別只會有一個實體物件存在,並提供單一的存取窗口。


Motivation

當我們必須確保某些類別只需要一個實體物件存在。譬如,地球上只會有一個台灣;樂陞只會有一個Gold Dragon使用龍歌雷射;遊戲中只需要有一個怪獸工廠來產生所有個怪獸。

雖然global variable或static variable很容易存取,但是我們無法保證不會出現多個同樣類別的物件。比較好的作法就是讓類別自己管理這唯一的實體物件。並保證絕對無法生成第二個的物件。


Implementation
class Singleton {
public:
static Singleton* GetInstance();
protected:
Singleton();
private:
static Singleton* mpInstance;
};

Singleton* Singleton::GetInstance() {
if (0 == mpInstance) {
mpInstance = new Singleton;
}
return mpInstance;
}

Singleton* Singleton::mpInstance = 0;


Singleton的建構式位於protected區域,因此外界無法產生物件實體。外界只能透過GetInstance()加以存取;如果 mpInstance為0,便會產生一個新的唯一物件。此作法稱為lazy initialization:也就是等到第一次使用時才去產生物件實體。當然我們也可以給靜態變數直接建構出實體物件,那麼GetInstance() 便可以直接回傳該指標即可。不過必須考慮到一些限制:

* 確保程式在執行過程中一定會使用該類別,否則就會浪費資源。
* 該類別或許會需要執行時產生的參數,而這些參數無法在靜態變數初始化時產生。
* Singleton之間不能有dependent relation,除非我們可以保證建構物件的順序。

class Singleton {
public:
static Singleton* GetInstance() { return mpInstance; }
protected:
Singleton();
private:
static Singleton* mpInstance;
};

Singleton* Singleton::mpInstance = new Singleton;


Problem

Q: Multi-thread架構中使用Singleton會出現什麼問題?
A: 可能產生兩個Singleton。


Solution

1. 靜態初始化Singleton
2. 將GetInstance()同步化,也就是同時間只有一個thread可以進入此函式