非虚接口习惯用法(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()
,因为它在Object
中被定义为{ v_load; }
,这当然会导致无限循环。此外,要求他们这样做可能会导致错误,因为他们忘记了这个电话。如果我想让他们启用这一点,我必须将v_load()
的访问指定为Object
中的protected
,这似乎是一个丑陋的解决方案,因为它会大大削弱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
关键字添加到Pawn
中的v_load
中,并使用另一个虚拟函数来允许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的输出