4/28/2010

Singleton Pattern

Singleton Pattern帶來的是保證一個class只有一個實體,無法產生第二個第三個... 使用這種模式的概念是系統中永遠只會唯一存在,如OS kernal、program core或hardward controller等。C++實作如下:
class IOController {
private:
    IOController() { /*implement*/ }
    IOController(const Core&);
    IOController& operator=(const IOController&);
    virtual ~IOController();

public:
    static IOController& getIOController() {
        static IOController IOController;
        return IOController;
    }

    void memberFunctionA() {}
    void memberFunctionA() {}

private:
    MemberData A;
    MemberData B;
};
一個正確的Singleton應該確保唯一性,C++確保唯一的保護措施有三點:
1. default constructor宣告為private並實作(空內容也沒關係)。因此實體只能在內部產生,外部無法取得建構子。
2. copy constructor and operator=宣告為private並不實作。宣告為private使得外部無法取得函式原型,不實作它是因為不需要,並且令編譯失敗(若使用到copy constructor or operator=,因為沒有實作,會產生連結錯誤,確保不能複製物件)
3. destructor宣告為private,防止外部刪除實體。(這一點可商議。若getIOController()回傳IOController*,則destructor必須宣告為private;若getIOController回傳IOController&,那這點可忽略)

另外值得注意的是getIOController()中宣告static IOController IOController物件,這樣寫法是依賴編譯器的一個規則,"函式內的靜態物件在該函式第一次執行時被初始化",所以getIOController從末被呼叫時,IOController沒有被初始化,效能並沒有降低。

提到唯一性一定會想起靜態函式和靜態物件,那Singleton可以利用靜態來實作嗎
class IOController {
public:
    static void memberFunctionA() {}
    static void memberFunctionA() {}
    // ...

private:
    static MemberData A;
    static MemberData B;
    // ...
};
這樣看似沒甚麼問題,也不需要宣告private之類有的沒的。但是這情況下會缺乏擴充性,假如現在需要第二代IOController2,且保留IOController,視不同情況選擇使用。這樣實作IOController2會很困難,因為靜態函式不能成為虛擬函式,所以IOController和IOController2不能使用多型。若以原來的範例則可以輕鬆解決。
class IOController2 : public IOController {
private:
    IOController2() { /*implement*/ }
    IOController2(const IOController2&);
    IOController2& operator=(const IOController2&);
    virtual ~IOController2();

public:
    static IOController2& getIOController2() {
        static IOController2 IOController2;
        return IOController2;
    }

    void memberFunctionA() {}
    void memberFunctionB() {}

private:
    MemberData C;
    MemberData D;
};
void main {
    bool condition = true;
    Core& core = condition? IOController2::getIOController2(): IOController::getIOController();

    core.memberFunctionA();
    core.memberFunctionB();
}

4/21/2010

陀飛輪

主誯:陳奕迅
作曲:Vincent Chow 填詞:黃偉文
編曲:Gary Tong 監製:Alvin Leong

過去十八歲 沒戴錶 不過有時間
夠我 沒有後顧 野性貪玩

霎眼廿七歲 時日無多 方不敢偷惰
宏願縱未了 奮鬥總不太晚

然後突然今秋
望望身邊 應該有 已盡有
我的美酒 跑車 相機 金錶 也講究
直到世間 個個也妒忌 仍不怎麼富有
用我尚有 換我沒有 其實已用盡所擁有

曾付出 幾多心跳
來換取 一堆堆的發票
人值得 命中減少幾秒 多買一隻錶
秒速 捉得緊了
而皮膚竟偷偷鬆了
為何用到盡了 至知哪樣緊要

勞力是 無止境
活著多好 不需要 靠物證
也不以高薪 高職 高級品 搏尊敬 wo~
就算搏到 伯爵那地位 和蕭邦的雋永
賣了任性 日拼夜拼 忘掉了為甚麼高興

曾付出 幾多心跳
來換取 一堆堆的發票
人值得 命中減少幾秒 多買一隻錶
秒速 捉得緊了
而皮膚竟偷偷鬆了
為何用到盡了 至知哪樣緊要

記住那關於光陰的教訓
回頭走天已暗
你獻出了十吋 時和分
可有換到十吋金

還剩低 幾多心跳
人面跟水晶錶面對照
連自己 亦都分析不了 得到多與少
也許 真的瘋了
那個倒影多麼可笑
靈魂若變賣了 上鏈也沒心跳

銀或金 都不緊要
誰造機芯 一樣了
計劃了 照做了 得到了 時間卻太少 no~

還剩低 幾多心跳
還在數 趕不及了
昂貴是這刻 我覺悟了
在時計裏 看破一生 渺渺

Memento Pattern

Mememto Pattern是一個簡單輕量的模式,基本上它十分簡單,也沒有必要複雜化。故名思義,這種模式是記錄一些回憶,過去資訊,目的是為了備份。在大型軟體中功能複雜而彈性,必定會提供使用者進行設定,在設定途中,總會有使用者想反悔不想執行改變設定,這種模式就是為了取消還原設定而存在的。
class CoreMemento {
    friend class Core;

private:
    CoreMemento() {}
    CoreMemento(const CoreMemento&);
    CoreMemento& operator=(const CoreMemento&);

    // member data
};

class Core {
public:
    // member function, getter/setter function

    CoreMemento getMemento();
    bool recovery(const CoreMemento&);
};

class ConfigDialog {
public:
    ConfigDialog(Core& core) : m_core(core) {
        m_memento = m_core.getMemento();
    }

    void OnCancel() {
        core. recovery(m_backup);
    }

    // member function, setting core function

private:
    Core& m_core;
    CoreMemento m_backup;
};
Memento class的實作如上所示,它沒有成員函式,只有成員變數記錄資料,亦沒有任何資料對外公開,甚至宣告copy constructor和operator=為私有不讓programmer複製物件,只宣告friend class讓Core作存取。因為實際上Memento class的用途只為了記錄Core的資料供還原使用,過多的公開是不必的,還可能誤導programmer認為Memento是可修改的,良好的設計應該在編譯期就檢查出來。

假設ConfigDialog是一個供使用者設定Core的UI介面,當使用者修改途中按取消鍵,會呼叫OnCancel(),這函式就把一開始的CoreMemento提供給Core作還原。

4/17/2010

Policy-Based Class Design

在C++新增template功能的初期,大家普遍認為template只是為物件導向設計得到更好的重用性,不單單將重複使用的程式碼設計成物件類別(class),連類別也重用,經由編譯器幫programmer把不同型別但功能完全相同的程式碼複製。如standard template library裡的vector,宣告vector就是一個存intergerarray的向量,宣告vector就是一個存character pointer array的向量,各自均在insert, earse等功能相同的介面。

其後有意無意間大家發現template能做的事情不單單如此,甚至出乎意料之外的強大,如使用LISP之類的技巧令編譯器幫你在編譯期運算,或編譯期才組合產生你所需要的程式碼。

在大型長期開發的軟體中,保持架構效能延展性介面一致十分重要,可是軟體設計必定不斷變動,功能的擴充、模組的更換尤其繁甚,為了加插功能、修改模組,結果往往就是架構變型,效能降低,介面不一致,元件肥大。(更可怕的是為求擴充,不斷新增介面,混亂不堪,日後維護困難,難以接手)

Policy-Based Class Design是一種新思維的設計方法,藉由template把各種小行為在編譯期組合成為功能複雜的元件,具有高度的效能和彈性。這種設計分為兩部份 ── policies, host class。policies是一些小型的類別,只單純負責某一核心功能,獨立運作的「策略」,如設計模式 (Design Pattern) 中的行為模式 (behavioral pattern) 或結構模式 (structural pattern),而host class像是一個外殼,由多個policies組成,針對特定主題,定義通用介面和錯綜複雜的邏輯流程,核心功能的細節完全經由組合的policies來實現。這種程式設計就像現實生活中的大公司,主管就是host class,為了完成專案找了幾個小將(policies)回來,排行程,分配資源,協調工作,開會擋箭,追殺廠商等,小將只要完成專案裡的需求就好了。(真完美)


現在來看看軟體裡經常會遇到的案例,假設軟體需要存儲設定,一開始決定使用ini格式,因此普遍會寫一個ConfigManager,提供寫入、擷取和操作介面。
class ConfigManager {
public:
    BOOL Load(char* path);
    void Write(char* path);

    BOOL Add(char* columeName, char* columeValue);
    char* columeValue Query(char* columeName);
};
如果考慮到延展性,ConfigManager將會繼承Load()和Write()這兩個單獨功能的類別。但這樣有一個缺點ConfigManager會繼承IniLoader和IniWriter裡所有公開介面,介面被破壞。
class ConfigManager : public IniLoader, public IniWriter {
public:
    BOOL Add(char* columeName, char* columeValue);
    char* columeValue Query(char* columeName);
};

class IniLoader {
public:
    virtual BOOL Load(char* path);
};

class IniWriter {
public:
    virtual BOOL Write(char* path);
};
除了使用多重繼承外,也可使用組合物件方式。
class ConfigManager {
public:
    BOOL Load(char* path) {
        if (IsExist(path))
            m_iniLoader.Load(path);
    }
    void Write(char* path) {
        if (IsExist(path))
            m_iniWriter.Write(path);
    }

    BOOL Add(char* columeName, char* columeValue);
    char* columeValue Query(char* columeName);

private:
    IniLoader m_iniLoader;
    IniWriter m_iniWriter;
};
其後軟體需要更換模組,假設由Ini改為Xml,還需要自動從Ini升級到Xml喔。所以可能會設計抽象類別BaseLoader和BaseWriter,IniLoader, XmlLoader, IniWriter, XmlWriter分別繼承抽象,並傳入ConfigManager中,讓上層去決定使用那種Loader和Writer。
class ConfigManager {
public:
    ConfigManager(BaseLoader* pLoader, BaseWriter* pWriter) {
        m_pLoader = pLoader; m_pWriter = pWriter;
    }

    BOOL Load(char* path) {
        if (IsExist(path))
            m_iniLoader.Load(path);
    }
    void Write(char* path) {
        if (IsExist(path))
            m_iniWriter.Write(path);
    }

    BOOL Add(char* columeName, char* columeValue);
    char* columeValue Query(char* columeName);

private:
    BaseLoader* m_pLoader;
    BaseWriter* m_pWriter;
};

class BaseLoader {
public:
    virtual BOOL Load(char* path) = 0;
};

class BaseWriter {
public:
    virtual BOOL Write(char* path) = 0;
};
現在的設計看起來一切都很好,可怕的事情發生了,針對Xml的讀入處理,ConfigManager需要提供一個介面Fix(),這時介面的一致性就被破壞了,如果使用一開始所說的多重繼承,則必須宣告四個不同的類別才能令介面保持一致性。如果未來還新增網路讀取功能NetLoader, NetWriter,就必須宣告 2 * 2 * 2 個不同類別了,這不就正好是template的能力嗎。

”令軟體存在數種不同版本的設計實作方案,每次只從中選擇其一的方案,而又需要保持切換與擴充的彈性”就是Policy-Based Class Design的中心思想。Policy-Based Class Design的設計如下,ConfigManager是host class,IniLoader, IniWriter, XmlLoader和XmlWriter是policies。
template<class Loader, class Writer>
class ConfigManager : public Loader, public Writer {
public:
    BOOL Load(char* path) {
        if (IsExist(path))
            m_iniLoader.Load(path);
    }
    void Write(char* path) {
        if (IsExist(path))
            m_iniWriter.Write(path);
    }

    BOOL Add(char* columeName, char* columeValue);
    char* columeValue Query(char* columeName);
};

class XmlLoader {
public:
    BOOL Load(char* path);
    BOOL Fix();
};
void main() {
    ConfigManager<Xmlloader, Iniwriter> c1;
    ConfigManager<Iniloader, Xmlwriter> c2;
    ConfigManager<Xmlloader, Netwriter> c3;

    c1.Load("xxx");
    c1.Fix();
    c2.Load("xxx");
    c2.Fix();    // compile error
    c3.Load("xxx");
    c1.Write("xxx");
    c2.Write("xxx");
    c3.Write("xxx");
}
看到這裡總會有點點卡住,到底Polymorphism Design和Policy-Based Class Design有甚麼差別呢。其實兩者之間有著極大的差異,如果說繼承體系是上而下 (top down)構成,介面由Base class定義,由Derived class實作,那麼Policy-Based Class就是下而上 (bottom up)構成,介面由Host class定義,由Policy class實作。Host class會繼承它所需的policies,並且在Host class中定義出操作行為的骨架流程,至於真正的實作細節,則全權委派 (delegate)給policies進行處理。架構恰好完全與繼承體系方向相反

上述例子中還不算最終形式的Policy-Based Class Design,就像遞迴一樣的概念,tempate也可以是多層的,經由Host class傳遞template parameter給polices來逹到,能組合出更多的功能。(這種組合可是次方級數成長的喔),如下,現在不再統一一起寫設定檔,改為每個元件單獨讀寫。
template<class Loader, class Writer, class Componet>
class ConfigManager : public Loader<Componet>, public Writer<Componet> {
public:
    BOOL Load(char* path) {
        if (IsExist(path))
            m_iniLoader.Load(path);
    }
    void Write(char* path) {
        if (IsExist(path))
            m_iniWriter.Write(path);
    }

    BOOL Add(char* columeName, char* columeValue);
    char* columeValue Query(char* columeName);
};

template<class Componet>
class IniLoader {
public:
    BOOL Load(char* path) {
        Componet::GetConfigInfo();
        // do something
    }
};
void main() {
    ConfigManager<XmlLoader, IniWriter, Schedular> c1;
    ConfigManager<IniLoader, XmlWriter, Recorder> c2;
    ConfigManager<IniLoader, NetWriter, RemoteServer> c3;

    c1.Load("xxx");
    c1.Fix();
    c2.Load("xxx");
    c2.Fix();    // compile error
    c3.Load("xxx");
    c1.Write("xxx");
    c2.Write("xxx");
    c3.Write("xxx");
}
說穿了Policy-Based Class Design的基本原理就是Strategy Pattern,如果現在需要的策略只有一個,那整個Host class就跟Strategy Pattern十分類似,也沒有必要使用Policy Design了。但是,如果現在需要的策略是兩個以上,還繼續使用Strategy Pattern反而會降低效能,元件肥大,日後難以維護。而Policy-Based Class Design就是使用template幫你組合程式碼,把多個Strategy Pattern組合成為一個類別,同時減低設計的相依性。

總結一下Policy-Based Class Design的優缺處
1. 高顆粒性 (granularity),核心功能獨立運作,方便unit test。
2. 高延展性。只要架構不變,新增功能、更換模組和小修改,只要寫Policy class就可以了。
3. 高擴充性。這是進階主題,下回再說。
4. 介面一致。每一個ConfigManger有一致的共用介面,定義在Host class裡,同時,對於特定某種ConfigManager能從繼承中獲得額外的介面,別與其他ConfigManager,又能保持介面完整。
5. 介面高彈性。這是進階主題,下回再說。
6. 高效能。每一個ConfigManager的介面和實作由繼承而來,避免透過成員變數中的抽象類別間接呼叫。值得注意的是Host, Policy class基本上是不用(也不需要)虛擬函式,直接免除virtual table的間接呼叫和記憶體使用量。
7. Host class實作技巧高。Host class使用到template,比直接寫一般的C++難。
8. 實作思想需要改變。Policy class非常獨立,常常是沒有任何成員變數,只實作行為,資料由參數傳入,回傳結果。