2012-01-25 3 views
2

Допустим, у меня есть следующий унаследованный код, что у меня есть возможность улучшить:Лучшая практика для определения интерфейса времени компиляции

class BaseInterfaceClass 
{ 

#if(HAS_FEATURE_A == 1) 
    void doFeatureA1() = 0; 
    void doFeatureA2() = 0; 
#endif 

#if(HAS_FEATURE_B == 1) 
    void doFeatureB1() = 0; 
    void doFeatureB2() = 0; 
    void doFeatureB3() = 0; 
#endif 

#if(HAS_FEATURE_C == 1) 
    void doFeatureC1() = 0; 
#endif 

    void doBaseFeature1() = 0; 
    void doBaseFeature2() = 0; 
}; 

Есть ли способ, чтобы покончить с #ifdef методом определения интерфейса при компиляции без увеличения времени выполнения нового дизайна? Другими словами, мне нужно сохранить тот факт, что производный класс, который компилируется только с HAS_FEATURE_A, не будет содержать код для HAS_FEATURE_B или HAS_FEATURE_C (по сравнению с кодом, связанным, просто не используемым из-за проверки времени выполнения).

Несколько замечаний

  1. Комфортные флаги не являются взаимоисключающими. Любая комбинация из них может быть определена.

  2. Как я понимаю, определение подкласса для каждой функции, а затем использование множественного наследования для объединения желаемых интерфейсов недостаточно. Помните, что производный класс должен иметь возможность реализовать любую комбинацию функций во время компиляции и не включать функцию, которая не определена для этой компиляции.

  3. Политическая стоимость изменения кода означает, что я должен быть в состоянии избавиться от всех директив или вообще не делать этого. Другими словами, недостаточно просто переместить определения #ifdef, чтобы сделать базовый класс более красивым.

  4. Существует достаточно сочетаний функций, которые могут быть нереалистичными. Я не собираюсь делать тысячи подклассов.

  5. Я не знаю шаблонов, но я хочу узнать, является ли это ключом.

+1

Почему нет множественного наследования? Это решило бы проблему очень элегантно. – Xeo

+0

Я вижу, что ломается так много неприятных способов, что не все они постыдные. –

ответ

2

Есть способы. Однако действительно ли они полезны?

Многие библиотеки используют условную компиляцию #if, чтобы предложить дополнительные функции выбора. Это давняя практика.

Возможно, вы сможете «удалить» это с помощью микширования, но не уверены, что в результате код станет более читаемым. Напротив, люди, использующие ваш код, теперь должны понимать, что происходит, когда это было просто очевидно.

С другой стороны, поскольку мы говорим о интерфейсах, вы можете просто захотеть разделить интерфейс на несколько компонентов (если возможно), каждый из которых посвящен ряду функций. Знать, можете ли вы или нет, потребовать больше знаний о тонкостях класса ... но это слишком похоже на объект Бога для меня.

1

Я бы определенно избежал использования препроцессора в этом случае.

Вы можете разделить свои функции на разные интерфейсы и выбрать класс выбора и выбрать именно те функции (и, следовательно, какие интерфейсы), которые он будет реализовывать.

Тогда вам, возможно, придется иметь другой интерфейс поиска для получения к функциям:

class IFeature 
{ 
}; 

class IFeatureA : public IFeature 
{ 
    virtual void DoSomething() = 0; 
}; 

class IFeatureB : public IFeature 
{ 
    virtual void DoSomethingElse() = 0; 
}; 

class IFullComponent 
{ 
    virtual void GetFeature(GUID featureId, IFeature** ppFeature) = 0; 
}; 

В этом случае ваши художественные интерфейсы должны извлечь из чего-то общего.

Да, это решение будет использовать множественное наследование (т. Е. Ваш конкретный компонент будет выводиться из интерфейсов IFeatureXX, а также IFullComponent), но даже для людей, которые избегают множественного наследования из-за воспринимаемой лишней сложности, помните, что вы только наследуете интерфейсы, а не фактическая реализация. Даже Java и C#, которые не поддерживают множественное наследование на уровне языка, позволяют вам это делать.

+0

Спасибо! Я думаю, что самые большие проблемы с множественным наследованием в этом случае заключаются в следующем: (1) каждый производный класс теоретически мог бы реализовать каждую функцию, поэтому каждому классу нужно было бы унаследовать каждую функцию, (2) класс, который хочет скомпилировать функцию * out * по-прежнему необходимо реализовать эту функцию, из-за 1. выше и, (3) я использовал void return для простоты, но сигнатуры и методы возврата функций сильно различаются. – dolphy

+0

@dolphy: почему (1) проблема для вас. Если у вас есть класс, который должен реализовать 10 интерфейсов, я не вижу ничего плохого в наследовании 10 раз. Однако с сигнатурами интерфейса, которые я предложил, вам даже не нужно это делать. Вы можете использовать каждую функцию в отдельном классе. Затем, если вы хотите удалить эту функцию из основного компонента, все, что вам нужно сделать, это изменить таблицу поиска в GetFeature() и удалить одну переменную-член. В качестве альтернативы вы можете посмотреть boost :: mpl :: inherit_linearly и изучить возможность генерации структуры наследования на основе красивого списка ... – DXM

+0

... но это немного сложнее, и я не буду вдаваться в это, Не уверен, что это даже то, что вы ищете. Кстати, я согласен с другим комментарием, что это похоже на то, что вы создаете объект God, и добавление каждой функции в отдельный класс также поможет в этом и сделает ваш код более управляемым. – DXM

1

В предположении, что вам необходимо сохранить BaseInterfaceClass вместо изменения наследования классов, используя его (в противном случае просто определить интерфейс для каждой функции и наследовать только те, которые вам нужны, это возможность). Вы можете использовать множественное наследование в сочетании со специализированными шаблонами, чтобы получить только те интерфейсные части, нужно:

template<bool B> class FeatureABase {}; 
template<> class FeatureABase<true> { 
public: 
    virtual void doFeatureA1() = 0; 
    virtual void doFeatureA2() = 0; 
}; 
//similar definitions for FeatureBBase and FutureCBase here 
class BaseInterfaceClass: public FeatureABase<HAS_FEATURE_A>, public FeatureBBase<HAS_FEATURE_B>, public FeatureCBase<HAS_FEATURE_C> 
{/*not flag dependent parts here*/}; 
//If you want to get rid of the preprocessor flags completely, you could define 
//BaseInterfaceClass as a template itself: 
template<bool HasA, bool HasB, bool HasC> 
class BaseInterfaceClass: public FeatureABase<HasA>, public FeatureBBase<HasB>, public FeatureCBase<HasC> 
{/*not flag dependent parts here*/}; 

Если вы не хотите использовать множественное наследование (так как вы не хотите, чтобы сделать объект больше) , вы можете раскатать его в один наследственной цепи:

template<bool B> class FeatureBBase: public FeatureABase<HAS_FEATURE_A> {}; 
template<> class FeatureBBase<true>: public FeatureABase<HAS_FEATURE_A> { 
public: 
    virtual void doFeatureB1() = 0; 
    virtual void doFeatureB2() = 0; 
    virtual void doFeatureB3() = 0; 
}; 
template<bool B> class FeatureCBase: public FeatureBBase<HAS_FEATURE_B> {}; 
template<> class FeatureCBase<true>: public FeatureBBase<HAS_FEATURE_B> { 
public: 
    virtual void doFeatureC1() = 0; 
} 
class BaseInterfaceClass: public FeatureCBase<HAS_FEATURE_C> 
{/*not flag dependent parts here*/}; 

Это предполагает, что ваши featureflags в основном логическое значение, так HAS_FEATURE_A является 0, если эта функция не включена. В противном случае вы можете выбрать тип шаблона int и быть специалистом на 1 вместо true. Если неиспользуемые флаги функций не определены, вы не можете полностью избавиться от использования #if или #ifdef, так как это единственный способ принимать решения на основе определения макроса или нет.

+0

Это интересно; Я должен подумать об этом немного, чтобы решить, будет ли это долгосрочная выгода ... в частности, существует смешанный метод использования флагов функций (определение 0 против просто не определено). Я думаю, что цепочка наследования будет неудобной в этой ситуации по нескольким причинам (добавив новые функции, новый разработчик понимает код, огромное количество функций). – dolphy

+0

@dolphy: Ну, для таких смешанных методов вы всегда можете идти по пути '#ifndef HAS_FEATURE_B'' #define HAS_FEATURE_B 0'. Я просто пытался показать, как вы могли бы заменить '# if' специальными шаблонами – Grizzly

Смежные вопросы