избегая перечисления в качестве идентификаторов интерфейса c ++ ООП - PullRequest
3 голосов
/ 25 марта 2010

Я работаю над структурой плагинов, использующей динамически загружаемые разделяемые библиотеки, которая основана на модели точек расширения Eclipse (и, возможно, других). Все плагины имеют схожие свойства (имя, идентификатор, версия и т. Д.), И каждый плагин теоретически может удовлетворить любую точку расширения. Фактическая обработка плагина (т.е. Dll) управляется другой библиотекой, и все, что я делаю, на самом деле - это управление наборами интерфейсов для приложения.

Я начал с использования enum PluginType для различения различных интерфейсов, но я быстро понял, что использование шаблонных функций сделало код намного чище и оставило бы основную работу на компиляторе, а не заставляло меня использовать много switch {...} заявления.

Единственная проблема заключается в том, что мне нужно указать одинаковую функциональность для членов класса - наиболее очевидный пример - плагин по умолчанию, который предоставляет определенный интерфейс. Класс Settings обрабатывает все настройки, включая плагин по умолчанию для интерфейса.

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

Как сохранить значение по умолчанию ISkin в контейнере, не прибегая к каким-либо другим средствам идентификации интерфейса?

Как я упоминал выше, в настоящее время я использую std::map<PluginType, IPlugin> Settings::defaults член для достижения этой цели (где IPlugin является абстрактным базовым классом, от которого происходят все плагины. Затем я могу dynamic_cast перейти к нужному интерфейсу, когда это необходимо, но это мне кажется, пахнет плохим дизайном и приносит больше вреда, чем пользы, я думаю.

будет приветствовать любые советы

edit: вот пример текущего использования плагинов по умолчанию

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 ]

1 голос
/ 25 марта 2010

Вместо этого вы можете использовать контейнер последовательностей boost :: variable (непроверенный иллюстративный код):

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 голосов
/ 25 марта 2010

В чем проблема перечисления? Отсутствие растяжимости.

Как обеспечить расширяемость и при этом сохранить идентификацию? Вам нужен полноценный объект, желательно с определенным типом.

В принципе вы можете сойти с рук:

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 голосов
/ 25 марта 2010

Я почти уверен, что вы можете сделать что-то подобное.

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 голосов
/ 25 марта 2010

Даункинг можно избежать с помощью Шаблон посетителя , но это может потребовать существенного рефакторинга вашей архитектуры. Таким образом, вы также не должны обращаться с плагинами по-другому. Создание экземпляров плагинов можно выполнить с помощью Factory . Надеюсь, что это даст вам некоторую отправную точку. Если вы хотите получить больше информации, вы должны предоставить больше информации о вашей архитектуре.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...