我正在使用动态加载的共享库来构建插件框架,该库基于Eclipse(以及其他的)扩展点模型。所有插件共享相似的属性(名称,ID,版本等),理论上每个插件都可以满足任何扩展点。实际的插件(即Dll)处理由另一个库管理,我所做的只是管理应用程序的接口集合。
我首先使用enum PluginType
来区分不同的接口,但我很快意识到使用模板函数可以使代码更清晰,并且可以将繁琐的工作留给编译器,而不是强迫我使用很多switch {...}
陈述。
唯一的问题是我需要为类成员指定类似的功能 - 最明显的例子是提供特定接口的默认插件。 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 :(得分:1)
您可以使用boost :: variant的序列容器(未经测试的说明性代码):
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);
}
答案 1 :(得分:0)
使用Visitor-Pattern可以避免向下转发,但这可能需要对您的架构进行大量重构。这样,您也不必以不同方式处理插件。可以使用Factory创建插件实例。希望能给你一些起点。如果您需要更多详细信息,则必须提供有关架构的更多信息。
答案 2 :(得分: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类尚未看到的类型,则当前实现将返回空指针。你必须以某种方式解释这一点。
答案 3 :(得分:0)
枚举有什么问题?缺乏可扩展性。
如何具有可扩展性并保留身份?你需要一个完整的物体,最好是特定类型的物体。
基本上你可以逃脱:
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
:)
我知道整个地方的转发都很丑陋,但是你只需要在一个方法GetDefault
中进行down_cast并在编译时检查它的类型。
更容易(让我们动态生成密钥):
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()
将解析为其他名称。