Разработка на основе политики и лучшие практики - C++
struct InkPen
{
void Write()
{
this->WriteImplementation();
}
void WriteImplementation()
{
std::cout << "Writing using a inkpen" << std::endl;
}
};
struct BoldPen
{
void Write()
{
std::cout << "Writing using a boldpen" << std::endl;
}
};
template<class PenType>
class Writer : public PenType
{
public:
void StartWriting()
{
PenType::Write();
}
};
int main()
{
Writer<InkPen> writer;
writer.StartWriting();
Writer<BoldPen> writer1;
writer1.StartWriting();
return 0;
}
Я написал вышеприведенный код как часть разработки политики на основе политики. У меня есть несколько вопросов по приведенному выше коду
1 - выглядит ли эта реализация правильно? Я имею в виду: действительно ли это похоже на дизайн на основе политики?
2 - Теперь я могу привязать любой вид ручки к писателю. Но что я буду делать, когда получу ручку без конструктора по умолчанию (только параметризованные конструкторы)? Как я справлюсь с этой ситуацией?
template<class PenType>
class Writer : public PenType
{
public:
void StartWriting()
{
PenType::Write();
}
};
3 - когда приведенный выше код используется как
Writer<InkPen> writer;
Я думаю, компилятор заменит PenType на InkPen. Если да, то почему я не могу вызвать только Write() из StartWriting() вместо префикса имени базового класса (PenType:: Write())?
4 - Я думаю, что основанный на политике дизайн заставляет вас наследовать классы, которые семантически недопустимы. В приведенном выше коде писатель получен из пера только потому, что писатель использует перо. Но говорить, что писатель - это ручка, семантически неверно. Есть ли другой лучший способ решить эту проблему, или я что-то здесь упускаю?
Какие-нибудь мысли?
4 ответа
Вот как я мог бы реализовать класс:
template<class PenType>
class Writer
{
public:
Writer(const PenType& pen = PenType()) : pen(pen) {}
void StartWriting()
{
pen.Write();
}
private:
PenType pen;
};
Это позволяет пользователю передавать конкретный объект Pen в конструктор, если у него либо нет конструктора по умолчанию, либо вы не хотите его использовать, и, во-вторых, он по-прежнему позволяет опустить объект PenType, если вы счастлив позволить ему создать его с конструктором по умолчанию. Стандартная библиотека C++ делает то же самое во многих классах (например, рассмотрим распределители для контейнерных классов).
Я удалил наследство. Похоже, он ничего не добавляет (и может вызвать проблемы. Возможно, вы не хотите, чтобы пользователь класса Writer вызывал функцию PenType::Write напрямую. Вместо этого вы можете использовать частное наследование, но часто композиция является более простой и традиционный дизайн.
Как правило, основанный на политике дизайн не требует наследования. Добавление его в качестве члена работает так же хорошо. Если вы идете за наследством, сделайте его частным, чтобы вы не получили проблему, которую вы упомянули как #4.
Это выглядит как хороший пример реализации интеллектуального указателя на основе политик: ссылка. Андрей Александреску описывает реализацию интеллектуального указателя на основе политики в одной из своих книг. Что касается ваших вопросов сейчас. У меня есть некоторый опыт в этом деле, но этого недостаточно, чтобы воспринимать мои слова как должное:
Объявление 1 и 4. Я предполагаю, что основанный на политике дизайн - это больше шаблоны, чем наследование. Вы пишете класс шаблона, а аргументы шаблона - это классы политики, например:
template<class FooPolicy, class BarPolicy>
class Baz {
// implementation goes here
};
Затем вы используете методы из классов политики в вашем классе:
void Baz::someMethod(int someArg) {
FooPolicy::methodInit();
// some stuff
BarPolicy::methodDone();
}
В этом примере я использую статические методы, потому что часто политика не требует какого-либо состояния. Если это так, вы включаете состояние политики по составу, а не по наследству:
template<class FooPolicy, class BarPolicy>
class Baz {
private:
FooPolicy::State fooState; // might require 'typename' keyword, I didn't
// actually tried this in any compiler
// rest of the Baz class
};
Объявление 2. Вы можете написать шаблон специализации - для определенной комбинации основного класса и его политик вы можете написать специальную версию любого метода или конструктора, AFAIK:
template <>
Baz<SomeConcreteFooPolicy, SomeConcreteBazPolicy>::Baz(someArgument)
: fooState(someArgument)
{
// stuff here
}
Надеюсь, это вам немного поможет,
Майк
Я знаю, что эта тема старая, но в первоначальном посте есть большой недостаток, и эта тема является одним из лучших результатов Google... так:
Не использовать public
наследование для дизайна на основе политики! Это будет означать "есть-а" вместо "имеет-а" / "использует-а". Поэтому вы должны использовать private
наследование!
1 - это реализация выглядит правильно? Я имею в виду, действительно ли это выглядит как дизайн на основе политики?
Политические классы получают свою полезность от сочетания поведения для создания разнообразных комбинаций. Когда у вас есть один такой шаблонный параметр, это не большой класс политики.
2 - Теперь я могу привязать любой вид ручки к писателю. Но что я буду делать, когда получу ручку без конструктора по умолчанию (только параметризованные конструкторы)? Как я справлюсь с этой ситуацией?
Опять же, это странный пример класса политики. Однако, чтобы напрямую ответить на ваш вопрос, вы можете предоставить конструктор, который принимает PenType. Вам, вероятно, также следует избегать наследования от PenType и хранить его как член (не нужно тесно связывать ваш класс политики с его политиками).
Я думаю, компилятор заменит PenType на InkPen. Если да, то почему я не могу вызвать только Write() из StartWriting() вместо префикса имени базового класса (PenType::Write())?
Когда вы наследуете от шаблона класса, вы должны указать this->member или BaseClass:: member.
4 - Я думаю, что основанный на политике дизайн заставляет вас наследовать классы, которые семантически недопустимы. В приведенном выше коде писатель получен из пера только потому, что писатель использует перо. Но говорить, что писатель - это ручка, семантически неверно. Есть ли другой лучший способ решить эту проблему, или я что-то здесь упускаю?
Храните PenType как член, как предложено выше. Всегда предпочитайте композицию наследованию, так как это позволяет избежать тесной связи наследования.