Объединение не виртуальных интерфейсов и многоуровневого наследования

Идиома невиртуального интерфейса (NVI) довольно понятна: вы не пишете public virtual функции, но public функции, которые вызывают private virtual Функция реализации, вот так:

class Object{
virtual void v_load();
public:
void load(){ v_load(); }
}

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

Теперь, когда вы являетесь производным автором, вы можете сами написать базовый класс — давайте назовем его Pawn — это распространяется на функциональность load() и поэтому должен переопределить v_load(), Но теперь вы столкнулись с проблемой:

Когда вы переопределяете v_load()другие клиенты, которые хотят получить данные из вашего класса, всегда перезаписывают это поведение и не могут Pawn::v_load() потому что это private функции, они также не могут вызывать Pawn::load() потому что это определяется как { v_load; } в Object что, конечно, приведет к бесконечной петле. Кроме того, требование к ним может привести к ошибкам, когда они забудут этот вызов. Если бы я хотел, чтобы они включили это, мне пришлось бы указать доступ к v_load() как protected в Objectчто кажется уродливым решением, так как это ослабит инкапсуляцию Object сильно.

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

Итак, как я могу дизайн Pawn таким образом, что клиенты все еще могут переопределить v_load() сохраняя возможность проверки предварительных условий или вызова других функций и (если возможно) не включая, не говоря уже о том, чтобы требовать клиентов Object или же Pawn позвонить в базу v_load() реализация?

2

Решение

  • Если вы намерены позволить людям «расширяться», а не «заменять» loadповедение, а затем положить код, который вы в настоящее время в v_load в load затем назовите пустой v_load в конце.
  • Или вы могли бы просто сделать v_load protected если вы хотите позволить людям выбирать между «заменой» или «расширением».
  • Если вы просто хотите позволить им заменить поведение, ваш код в порядке.

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

Если вы хотите ограничить переопределение вашим Pawn дочерний класс, добавьте final ключевое слово для v_load в Pawn и использовать другую виртуальную функцию, чтобы позволить детям Pawn настроить его поведение.

2

Другие решения

Как насчет смешивания в некоторых CRTP?

#include <iostream>

class BaseObject
{
private:
virtual void v_load() = 0;

public:
void load() { v_load(); }
};

template<typename Derived>
class Object : public BaseObject
{
private:
virtual void v_load() { static_cast<Derived&>(*this).load(); }
};

class Pawn : public Object<Pawn>
{
public:
void load() { std::cout << "Pawn::load()" << std::endl; }
};

class BlackPawn : public Pawn
{
private:
virtual void v_load() {
std::cout << "BlackPawn::v_load()" << std::endl;
std::cout << "- "; Pawn::load();
}

public:
void load() {
std::cout << "BlackPawn::load()" << std::endl;
std::cout << "- "; Pawn::load();
}
};

class BigBlackPawn : public BlackPawn
{
private:
virtual void v_load() {
std::cout << "BigBlackPawn::v_load()" << std::endl;
std::cout << "- "; BlackPawn::load();
}

public:
void load() {
std::cout << "BigBlackPawn::load()" << std::endl;
std::cout << "- "; BlackPawn::load();
}
};

template<typename T>
void load(T& x)
{
x.load();
}void vload(BaseObject& x)
{
x.load();
}

int main()
{
Pawn p;
BlackPawn bp;
BigBlackPawn bbp;

load(p);
load(bp);
load(bbp);
std::cout << std::endl;
vload(p);
vload(bp);
vload(bbp);
}

Выход на ideone.

0

По вопросам рекламы [email protected]