避免使用枚举作为接口标识符c ++ OOP

时间:2010-03-25 13:36:10

标签: c++ oop templates plugins

我正在使用动态加载的共享库来构建插件框架,该库基于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));
}

4 个答案:

答案 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()将解析为其他名称。