2011-06-16 46 views
1

在MFC中使用Visual Studio C++。我試圖找出什麼是存儲應用程序/程序設置的好方法。我並不是指它們的持久存儲,而是指代碼中用於保存設置的數據結構。應用程序/程序設置的C++類?

我創建了一個名爲Settings的靜態類,它有幾個靜態方法和嵌套類來分區設置。例如:

Settings::General::GetHomePage(); 

現在我進入單元測試,我開始認識到靜態類是不可取的:

class Settings 
{ 
public: 
    Settings(void); 
    ~Settings(void); 

    static void SetConfigFile(const char * path); 
    static CString GetConfigFilePath(); 
    static void Load(); 
    static void Save(); 

    class General 
    { 
    public: 
     static CString GetHomePage(); 
     static void SetHomePage(const char * url); 
    private: 
     static int homePageUrl_; 
    }; 

private: 
    static CString configFilePath_; 
}; 

然後我可以在我的代碼,就像訪問我的設置。所以我想把它變成一個基於實例的類。但是我必須管理嵌套的類實例,這些實例雖然很簡單,但對於測試來說似乎仍然有點麻煩。嵌套類的全部意圖僅僅是將設置分組爲邏輯組。我在辯論一個基於字符串的設置類是否會更好,比如settings-> get(「General.HomePage」),儘管我認爲我更喜歡專用存取方法的強類型。

所以爲了解決我的問題什麼是一個好的數據結構來保存支持直接單元測試的程序配置/設置?

回答

3

你可以做到這一點,如果它適合你。你可以拋開枚舉並轉到const字符串甚至自由格式的字符串。枚舉也不一定要在類中定義。有很多方法可以做到這一點。

如果您想實現類別,另一個類可以使用模板來定義枚舉類型,而多個實例可以做類似的事情。

只是一個想法。

#include "stdafx.h" 
#include <map> 
#include <string> 
#include <iostream> 
using namespace std; 


class Settings 
{ 
public: 

    typedef enum 
    { 
    HomePageURL, 
    EmailAddress, 
    CellPhone 
    } SettingName; 

private: 
    typedef map<SettingName, string> SettingCollection; 

    SettingCollection theSettings; 


public: 

    string& operator[](const SettingName& theName) 
    { 
    return theSettings[theName]; 
    } 

    void Load() 
    { 
    theSettings[HomePageURL] = "http://localhost"; 
    } 

    void Save() 
    { 
    // Do whatever here 
    } 

}; 


int _tmain(int argc, _TCHAR* argv[]) 
{ 
    Settings someSettings; 

    someSettings.Load(); 

    cout << someSettings [Settings::SettingName::HomePageURL] << endl; 


    return 0; 
} 
+0

在C++中恕我直言,使用'typedef enum bla {} foo'確實沒有意義。聲明變量時,不需要編寫'enum bla varname'。 'bla varname'就夠了。 – RedX 2011-06-17 07:51:40

+0

@RedX:在這個玩具的例子中,我同意。然而,通常情況下,隨着代碼的發展,你會發現你的模板參數並不適合你的需求。將SettingCollection :: mapped_type或SettingCollection :: key_type引用爲類型會更容易,因此如果在複雜的代碼段中更改模板參數,則只能在一個點上更改類型。如果您在本身是模板的類中使用了模板類型,這也很有效。它還顯示了該類型的用途,因爲它在整個代碼中都被明確地用於地圖中。 – Nathan 2011-06-17 17:43:23

2

我不認爲必須有你的要求之間的衝突:(1)配置變量提供類型安全訪問;和(2)使用"fully.scoped.name"語法來指定配置變量的名稱。當然,你可以有類型安全的操作,例如:通過讀取第二章和第三章的的3

const char * getString(const char * fullyScopedName); 
int   getInt(const char * fullyScopedName); 
bool   getBool(const char * fullyScopedName); 

你可能會找到一些靈感入門指南PDFHTML)我Config4Cpp庫。

編輯:本Config4Cpp文檔我提到可能會提供API的設計靈感,但我遲來的意識到你可能的情況下,您決定從頭開始編寫自己的配置類上實現選項欣賞意見(而不是使用第三方三方庫像Config4Cpp)...

你的類應該使用std::map存儲的fullyScopedName->值映射的集合。顯然,fullyScopedName將是一個字符串,但有兩種選擇來表示

第一個選項是將表示爲字符串。類型安全的訪問器(如getInt()getBool())將從映射中檢索基於字符串的值,然後解析它以將其轉換爲所需的類型。如果解析失敗,那麼訪問器操作會引發異常。 (即由Config4Cpp採取的做法。)

第二個選項是代表如下面的僞代碼:

enum ValueType { STRING_VAL, INT_VAL, BOOL_VAL }; 
struct Value { 
    ValueType   type; 
    union { 
     const char * stringVal; 
     int   intVal; 
     bool   boolVal; 
    } data; 
}; 

類型安全訪問的實現可以然後被編碼爲如下(僞代碼):

int getInt(const char * fullyScopedName) 
{ 
    Value * v = nameValueMap[fullyScopedName]; 
    if (v->type != INT_VAL) { 
     throw WrongTypeException(...); 
    } 
    return v->data.intVal; 
} 
0

這是我使用的類現在大多由Nathan的回答啓發除與模板方法:

class Settings { 
public: 
    Settings(void); 
    virtual ~Settings(void); 

    enum SettingName { General_WindowWidth, General_HomePageUrl, 
     General_ShowDownloadsWindow, Privacy_RememberPasswords, 
     Privacy_RememberHistory }; 

    virtual void SetConfigFile(const char * path); 
    virtual std::string GetConfigFilePath(); 
    virtual void Load(); 
    virtual void Save(); 

    template<class T> 
    T Get(SettingName name) { 
     return boost::lexical_cast<T>(settings_[name]); 
    } 

    template<class T> 
    void Set(SettingName name, T value) { 
     settings_[name] = boost::lexical_cast<std::string>(value); 
    } 

    void Set(SettingName name, std::string value) { 
     settings_[name] = value; 
    } 

private: 
    std::string configFilePath_; 
    std::map<SettingName, std::string> settings_; 
};