2011年1月7日星期五

[C++編程心德] Config Class

寫應用程式最常遇到的功能之一是設定界面,讓用戶可以按他們的喜好改變軟件的運作方式。

在MVC的Design pattern慣例下,一般都會建立一個Config class去統一儲存所有的選項,像是以下的程式碼就很普遍。


class SimpleConfig {
public:

    void save();
    void load();

    void setOptionA(int val);
    int getOptionA();

    void setOptionB(double val);
    double getOptionB();

    void setOptionC(bool val);
    bool getOptionC();

private:
    int optionA;
    double optionB;
    bool optionC;
};

我也寫過類似的,不過很快就發覺那樣做很傻,每次增加選項都要c&p一堆代碼,然後當選項增至百來項時……嘿, 擁有數百個成員函數的Class看起來跟怪物沒二樣。

自從見識過幾次後,我就痛定思痛決不讓怪物再次出現。

事實上要改良以上的代碼其不困難,只要懂得使用Variant及Enum type。

Variant type

Variant type本身是一種資料類型,卻沒有固定的儲存方法,它可以是int、也可是double,就算是String或其他複雜資料類型也沒問題,雖然執行效率會比primitive type為低,使用起來卻非常方便,不過可惜C/C++並不支援。

故這得依懶其他library framework去實現,像gtk+及Qt這類都有各自的Variant type,除非堅持要寫純C++,否則就別自尋煩惱吧。

Enum type

為每個選項加入setter/getter會無可避免地令Class變得臃腫, 如果把Enum及Variant配合使用,就能大大簡化程式碼的複雜度:

class SimpleConfigImproved {
public:
    enum Option {
OptionA,
        OptionB,
        OptionC,
        Last
    } ;

    void save();
    void load();

    QVariant get(Option option);
    void set(Option option,QVariant val);

private:
    map <Option,QVariant> table;
};

註:QVariant為Qt的variant type

所有選項都用一個Enum的值去代表,只有一個setter及一個getter,每次增加選項都只不過是在宣告裏加一行代碼而已,這比之前的版本簡潔許多。

儲存

另外有一個以上未有考慮的事情就是儲存的方法,每個選項都要有一個名字,通常都會跟程式一樣,例如以上的程式碼可能會這樣儲存:
OptionA=0
OptionB=0.0
OptionC=false
問題是C/C++並沒有提供直接的方法可以讓你把Enum的值變成文字,你必須要找一個辦法讓程式碼中的”OptionA”能轉化成文字,否則就可以要寫下那麼愚蠢的程式:
write(“OptionA”,table[OptionA]);
write(“OptionB”,table[OptionB]);
write(“OptionC”,table[OptionC]); 
每加一個選項時記得再C&P一次......

關於這個問題有許多的方法去解決,基於我是個懶人,而最近也在寫Qt,在Qt裏只要加上Q_ENUMS宣告就能幫你把Enum的值變成QString,用很簡單的程式碼就能完成儲存的工作:
   for (int i = OptionA ; i < Last ; i++ ){
        write( nameOfOption( (Option) i) );
    }
加多少選項都不用重寫儲存的程式碼! 

宣告

class ConfigQt : public QObject {
    Q_OBJECT
    Q_ENUMS(Option)

public:
    enum Option {
        OptionA,
        OptionB,
        OptionC,
        Last
    } ;

    void save();
    void load();

    QVariant get(Option option);
    void set(Option option,QVariant val);

    static QString nameOfOption(Option val);

private:
    QMap <Option,QVariant> table;
};

QString ConfigQt::nameOfOption(ConfigQt::Option val) {
    QMetaEnum metaEnum = staticMetaObject.enumerator(0);
    return metaEnum.key(val);
}

 
 

1 則留言:

無名 說...

通常我會用 Serialize 的方法變成 JSON object 就最簡單方便了

Creative Commons License
本網誌Ben Lau製作,以共享創意署名-非商業性-相同方式共享 3.0 香港 授權條款釋出。