假设我使用NVI习语具有以下层次结构:
class Base
{
public:
virtual ~Base() {}
void foo() { cout << "Base::foo" << endl; foo_impl(); }
private:
virtual void foo_impl() = 0;
};
class A : public Base
{
private:
virtual void foo_impl() { cout << "A::foo_impl" << endl; }
};
如果在层次结构中的某个时刻,我想&#34;添加&#34;非虚拟基本方法中的不变量,最好的方法是什么?
一种方法是在SpecialBase级别递归NVI习语:
class SpecialBase : public Base
{
private:
void foo_impl() { cout << "SpecialBase::foo" << endl; bar_impl(); }
virtual void bar_impl() = 0;
};
class B : public SpecialBase
{
private:
virtual void bar_impl() { cout << "B::bar_impl" << endl; }
};
但我并不喜欢这个想法,因为我不想为我添加到我的层次结构中的每个派生基础添加方法(使用不同的名称)...
另一种方法是拥有以下(不是NVI):
class Base
{
public:
virtual ~Base() {}
virtual void foo() { base_foo(); foo_impl(); }
protected:
void base_foo() { cout << "Base::foo" << endl; }
virtual void foo_impl() = 0;
};
class SpecialBase : public Base
{
public:
virtual void foo() { base_foo(); specialbase_foo(); foo_impl(); }
protected:
void specialbase_foo() { cout << "SpecialBase::foo" << endl; }
};
class B : public SpecialBase
{
private:
virtual void foo_impl() { cout << "B::foo_impl" << endl; }
};
在我看来,这一点不那么令人困惑,因为在任何时候,具体类只需要实现虚方法,而派生基类如果选择也可以覆盖基(虚)方法。
还有另一种更清洁的方法来实现同样的目标吗?
修改
我正在寻找一种非常通用的设计模式,可以让我拥有以下类型的层次结构:
Base <- A
<- B
<- SpecialBase <- C
<- D
<- VerySpecialBase <- E
<- StrangeBase <- F
每个Base
类可以(并将覆盖foo),而类A-F
只需要重新实现foo_impl
。
请注意,添加另一个可选的自定义虚拟函数(例如bar_impl
)在这里不会有帮助,因为它只允许一个额外的自定义层,在那里我可能需要无限数量。
答案 0 :(得分:0)
根据我的理解,NVI是一种防止/阻止向非虚拟基本方法添加不变量的方法,因此此时要添加不变量的事实表明NVI要么不是您要查找的模式完全没有,或者你可能想要重构你的设计,这样你就不需要添加这样的不变量。
这就是说,简单地将以前的非虚拟接口设置为虚拟的替代方法是使用C ++ 11中的final关键字:
class Base
{
public:
virtual ~Base() {}
virtual void foo() { base_foo(); foo_impl(); }
protected:
void base_foo() { cout << "Base::foo" << endl; }
virtual void foo_impl() = 0;
};
class SpecialBase : public Base
{
public:
virtual void foo() final // note the use of 'final'
{ base_foo(); specialbase_foo(); foo_impl(); }
protected:
void specialbase_foo() { cout << "SpecialBase::foo" << endl; }
};
class B : public SpecialBase
{
private:
virtual void foo_impl() { cout << "B::foo_impl" << endl; }
};
这里NVI不是由Base类实现的,而是在SpecialBase级别实现的,因为从SpecialBase派生的类不能再覆盖公共接口(即foo)。
通过这种方式我们说Base的公共接口被允许被覆盖(可以添加不变量,甚至可以重新实现整个函数),但是SpecialBase的公共接口不是。
就我个人而言,我发现这在某些有限的情况下很有用,但大多数时候我只是想在Base中使用更完整的界面。
最终我认为使用Base来清楚地定义允许的自定义点是更常见的:
class Base
{
public:
virtual ~Base() {}
virtual void foo() { base_foo(); bar_impl(); foo_impl(); }
protected:
void base_foo() { cout << "Base::foo" << endl; }
virtual void bar_impl() {} // bar_impl is an optional point of customization
// by default it does nothing
virtual void foo_impl() = 0; // foo_impl is not optional derived classes
// must implement foo_impl or else they will be abstract
};
class B : public Base
{
private:
virtual void bar_impl() { cout << "SpecialBase::foo" << endl; }
virtual void foo_impl() { cout << "B::foo_impl" << endl; }
};
请注意,根本不再需要SpecialBase类层。
答案 1 :(得分:0)
有人向我建议了这篇帖子,类似于前几天我浏览的与NVI有关的内容,因此是死灵。
我建议在基类中添加一个Check-Adding机制,以便派生类可以添加需求。只要可以使用基类访问功能来测试需求,这就能以非常简单的方式起作用,否则,您特殊的MyInvariant类必须对doCheckInvariantOK()的基参数进行dynamic_cast才能使不变量起作用。
edit:我理解“不变式”遵循foo()的前置条件和后置条件,就像在形式验证中一样。如果要在base_foo()之前和/或之后添加功能(我认为实际上是在此之后),则可以类似的方式进行。
class Base
{
public:
virtual ~Base() {}
void foo()
{
cout << "Base::foo" << endl;
//Can use invariants as pre and/or postconditions for foo_impl
for(const std::unique_ptr<InvariantBase>& pInvariant : m_invariants)
{
//TODO cout << "Checking precondition " << pInvariant->GetDescription() << endl;
if(!pInvariant->CheckInvariantOK(*this))
{
//Error handling
}
}
foo_impl();
}
protected:
void AddInvariant(std::unique_ptr<InvariantBase>&& pInvariant)
{
m_invariants.push_back(std::move(pInvariant));
}
struct InvariantBase
{
bool CheckInvariantOK(const Base& base)
{
return doCheckInvariantOK(base);
}
private:
virtual bool doCheckInvariantOK(const Base& base) = 0;
};
private:
std::list<std::unique_ptr<InvariantBase>> m_invariants;
virtual void foo_impl() = 0;
};
class A : public Base
{
private:
virtual void foo_impl() { cout << "A::foo_impl" << endl; }
};
class SpecialBase : public Base
{
public:
SpecialBase()
: Base()
{
AddInvariant(std::unique_ptr<MyInvariant>(new MyInvariant() ) );
}
private:
void foo_impl() { cout << "SpecialBase::foo" << endl; bar_impl(); }
virtual void bar_impl() = 0;
struct MyInvariant : public InvariantBase
{
virtual bool doCheckInvariantOK(const Base& base) override
{
//TODO: special invariant code
}
};
};
class B : public SpecialBase
{
private:
virtual void bar_impl() { cout << "B::bar_impl" << endl; }
};