Идиома невиртуального интерфейса (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()
реализация?
load
поведение, а затем положить код, который вы в настоящее время в v_load
в load
затем назовите пустой v_load
в конце.v_load
protected
если вы хотите позволить людям выбирать между «заменой» или «расширением».В качестве бонуса, во всех этих 3 вариантах вы можете изменить «разрешить» с «силой», сделав v_load
чисто виртуальный, если у вас нет поведения по умолчанию.
Если вы хотите ограничить переопределение вашим Pawn
дочерний класс, добавьте final
ключевое слово для v_load
в Pawn
и использовать другую виртуальную функцию, чтобы позволить детям Pawn
настроить его поведение.
Как насчет смешивания в некоторых 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.