2010-03-25 27 views
3

我正在使用基於Eclipse(和其他的)擴展點模型的動態加載共享庫的插件框架。所有插件共享類似的屬性(名稱,ID,版本等),每個插件理論上可以滿足任何擴展點。實際的插件(即Dll)處理由另一個庫管理,我所做的只是管理應用程序接口的集合。避免枚舉作爲接口標識符c + + OOP

我開始使用enum PluginType來區分不同的接口,但我很快意識到,使用模板函數使得代碼更加清晰,並且將繁重的工作留給編譯器,而不是迫使我使用大量的聲明。

唯一的問題是我需要爲類成員指定類似的功能 - 最明顯的例子是提供特定接口的默認插件。一個Settings類處理所有設置,包括接口的默認插件。

Skin newSkin = settings.GetDefault<ISkin>();

如何存放在容器中的默認ISkin而不訴諸識別接口的一些其他手段?

正如我上面提到的,我當前使用std::map<PluginType, IPlugin> Settings::defaults構件來實現這一(其中IPlugin是一個抽象基類,所有的插件從派生。然後我可以dynamic_cast到所需的接口需要時,但是這確實味道不好設計的我和介紹弊大於利我覺得

歡迎任何提示

編輯:這裏是當前使用的默認插件的例子

typedef boost::shared_ptr<ISkin> Skin; 
typedef boost::shared_ptr<IPlugin> Plugin; 
enum PluginType 
{ 
    skin, 
    ..., 
    ... 
} 

class Settings 
{ 
public: 
    void SetDefault(const PluginType type, boost::shared_ptr<IPlugin> plugin) { 
    m_default[type] = plugin; 
    } 
    boost::shared_ptr<IPlugin> GetDefault(const PluginType type) { 
    return m_default[type]; 
    } 
private: 
    std::map<PluginType, boost::shared_ptr<IPlugin> m_default; 
}; 

SkinManager::Initialize() 
{ 
    Plugin thedefault = g_settings.GetDefault(skinplugin); 
    Skin defaultskin = boost::dynamic_pointer_cast<ISkin>(theskin); 
    defaultskin->Initialize(); 
} 

我寧願調用getdefault,如下所示,並自動轉換爲派生類。不過,我需要專門爲每個類的類型。

template<> 
Skin Settings::GetDefault<ISkin>() 
{ 
    return boost::dynamic_pointer_cast<ISkin>(m_default(skin)); 
} 
+0

你可以發表一些示例代碼來說明如何使用默認值?我很難想象你在說什麼。 – 2010-03-25 13:41:09

+0

如果'IPlugin'是一個抽象類,則需要在地圖中存儲指針(無論如何您都需要這樣做,否則就會出現切片問題)。 – 2010-03-25 13:43:48

+0

嗨克里斯托,增加了更多的例子 – AlasdairC 2010-03-25 14:00:05

回答

0

enum的問題是什麼?缺乏可擴展性。

如何具有可擴展性並保留標識?你需要一個完整的對象,最好是一個特定的類型。

基本上你可以逃脫:現在

class IPluginId 
{ 
public: 
    virtual IPluginId* clone() const = 0; 
    virtual ~IPluginId(); 

    bool operator<(const IPluginId& rhs) const { return mId < rhs.mId; } 
    bool operator==(const IPluginId& rhs) const { return mId == rhs.mId; } 

protected: 
    static size_t IdCount = 0; 
    IPluginId(size_t id): mId(id) {} 
private: 
    size_t mId; 
}; 

template <class Plugin> 
class PluginId 
{ 
public: 
    PluginId(): IPluginId(GetId()) {} 
    IPluginId* clone() const { return new PluginId(*this); } 
private: 
    static size_t GetId() { static size_t MId = ++IdCount; return MId; } 
}; 

,作爲使用,它將獲得:

// skin.h 

class ISkin; 

struct SkinId: PluginId<ISkin> {}; // Types can be forward declared 
            // Typedef cannot 

class ISkin: public IPlugin { /**/ }; 

現在你可以使用:

class Settings 
{ 
public: 
    template <class Plugin> 
    void SetDefault(boost::shared_ptr<Plugin> p); 

    template <class Plugin> 
    boost::shared_ptr<Plugin> GetDefault(const PluginId<Plugin>& id); 

private: 
    boost::shared_ptr<IPlugin> GetDefault(const IPluginId& id); 
}; 

的模板版本以非模板版本實現,並自動執行向下轉發。指針可能是錯誤的類型,因爲編譯器會執行類型檢查,所以你可以用static_cast :)

我知道在各處都是向下傾斜是一種醜陋,但在這裏你只是down_cast一種方法是GetDefault,它的類型在編譯時檢查。

甚至更​​容易(讓我們動態生成的鍵):

class Settings 
{ 
public: 
    template <class Plugin> 
    void SetDefault(const boost::shared_ptr<Plugin>& p) 
    { 
    mPlugins[typeid(Plugin).name()] = p; 
    } 

    template <class Plugin> 
    boost::shared_ptr<Plugin> GetDefault() const 
    { 
    plugins_type::const_iterator it = mPlugins.find(typeid(Plugin).name()); 
    if (it == mPlugins.end()) return boost::shared_ptr<Plugin>(); 

    return shared_static_cast<Plugin>(it->second); 
    } 

private: 
    typedef std::map<std::string, std::shared_ptr<IPlugin> > plugins_type; 
    plugins_type mPlugins; 
}; 

但是它比第一替代不太安全,尤其是你可以把任何東西存在,只要它繼承IPlugin,所以你可以把例如MySkin,並且您將無法通過ISkin檢索它,因爲typeid(T).name()將解析爲其他名稱。

+0

嗨Matthieu, 感謝您的答案。從後者開始,我非常喜歡使用'type_info.name'屬性的想法,它一次性完全解決了我的問題。我實際上是在今天看着typeid之類的東西,並沒有做出信心的飛躍,意識到我可以以許多不同的方式將它用作財產。 但是我並不完全遵循你的第一個建議,如果你仍然熱衷於[:)]你能提供兩個GetDefault方法的實現嗎? 如果可能的話,我絕對傾向於將它作爲static_cast來使用 – AlasdairC 2010-03-25 23:02:56

+0

很高興爲您提供幫助:)如果您想爲常見id值使用多個插件類型,您可以使用'multimap'。 – 2010-03-26 08:02:28

+0

我不知道這是否可能,但有些情況下我想從「ISkin」中獲取'SkinId'類型(例如)。 有沒有什麼辦法可以避免將'IPluginId'作爲參數傳遞給'GetDefault'方法?即通過返回類型聲明的'T'參數來解析類型? 目前,我必須通過ID像下面 '的boost :: shared_ptr的皮膚= settings.GetDefault(SkinId());' – AlasdairC 2010-03-26 11:47:28

0

向下轉換可以通過使用Visitor-Pattern是可以避免的,但是這可能需要架構的實質性重構。這樣,您也不必以不同的方式處理插件。創建插件的實例可以使用Factory完成。希望能給你一些起點。如果您希望獲得更多詳細信息,則必須提供有關您的架構的更多信息。

+0

嗨, 它看起來像訪客模式可能是一個更好的選擇,因爲每個插件理論上可以提供任何接口。然而,我有點撕裂,因爲要實現這一點需要進行重大的重構。 – AlasdairC 2010-03-25 14:17:35

1

你可以使用升壓序列容器::變種,而不是(未經測試示例代碼):

tyepdef boost::variant< 
boost::shared_ptr<ISkin>, 
boost::shared_ptr<IPluginType2>, 
boost::shared_ptr<IPluginType3>, 
etc...> default_t; 
std::deque<default_t> defaults; 

然後:

template <class T> 
boost::shared_ptr<T> GetDefault() { 
    for(std::deque<default_t>::iterator it = defaults.begin(), endIt = defaults.end(); 
     it != endIt; 
     ++it) { 
     boost::shared_ptr<T>* result = boost::get< boost::shared_ptr<T> >(*it); 
     if(result) { 
      return *result; 
     } 
    } 
    return boost::shared_ptr<T>(0); 
} 
+0

這看起來像一個很好的解決方案,感謝您的建議 – AlasdairC 2010-03-25 14:03:50

0

我敢肯定,你可以做這樣的事情。

class Settings 
{ 
    public: 
     // ... 
     template <class T> 
     boost::shared_ptr<T> GetDefault() 
     { 
      // do something to convert T to an object (1) 
      return m_default[T_as_an_obj]; 
     } 
     // .... 
}; 

SkinManager::Initialize() 
{ 
    boost::shared_ptr<ISkin> defaultskin = g_settings.GetDefault<ISkin>(); 
    defaultskin->Initialize(); 
}   

行(1)是我認爲我曾經見過的部分,但不知道如何做自己。另請注意,如果您傳遞的是Settings類尚未顯示的類型,則當前實現將返回空指針。你必須以某種方式來解釋這一點。

+0

嗨克里斯托,是的,我總是檢查指針後的安全。像你說的第1行是最困難的部分,不需要使用switch語句,typeid(T)等。 – AlasdairC 2010-03-25 22:52:24