将非虚拟接口和多级继承结合在一起

时间:2014-08-21 14:35:22

标签: c++ inheritance c++11 encapsulation non-virtual-interface

非虚拟接口idiome(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,这似乎是一个丑陋的解决方案,因为它会削弱{{}的封装1}}非常。

你当然可以覆盖Object来调用一个新函数v_load(),然后被客户端覆盖,但这似乎非常容易出错,因为很多客户端可能会重载错误的函数

那么,我如何设计v_pawnLoad()以使客户端仍然可以覆盖Pawn,同时保持检查前置条件或调用其他函数以及(如果可能)不启用的能力,让单独要求v_load()Object的客户调用基础Pawn实施?

2 个答案:

答案 0 :(得分:2)

  • 如果您的意图是允许人们“延伸”而不是“替换”load的行为,那么请将v_load中当前拥有的代码放入load然后调用最后是空v_load
  • 如果您想让人们在“替换”或“延伸”之间做出选择,您可以v_load protected
  • 如果您只是想让它们替换行为,那么您的代码就可以了。

作为奖励,在所有这三种变体中,如果您没有默认行为,则可以通过使v_load成为纯虚拟来更改“允许”。

如果您希望将覆盖范围限制在Pawn子级别,请将final关键字添加到v_load中的Pawn,并使用其他虚拟函数来允许Pawn的子级{1}}自定义其行为。

答案 1 :(得分:0)

mixin'怎么样?在一些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上的输出。