在C ++中,接口可以由一个类实现,其所有方法都是纯虚拟的。
这样的类可以是库的一部分,用于描述对象应该实现哪些方法以便能够与库中的其他类一起使用:
class Lib::IFoo
{
public:
virtual void method() = 0;
};
class Lib::Bar
{
public:
void stuff( Lib::IFoo & );
};
现在我想使用类Lib :: Bar,所以我必须实现IFoo接口。
为了我的目的,我需要一个完整的相关类,所以我想使用一个基类来保证使用NVI习语的常见行为:
class FooBase : public IFoo // implement interface IFoo
{
public:
void method(); // calls methodImpl;
private:
virtual void methodImpl();
};
非虚拟接口(NVI)习惯用法应该拒绝派生类覆盖FooBase::method()
中实现的常见行为的可能性,但由于IFoo
使其成为虚拟,所以派生类似乎都具有有机会覆盖FooBase::method()
。
如果我想使用NVI习语,除了已经建议的pImpl习语之外还有哪些选择(感谢space-c0wb0y)。
答案 0 :(得分:5)
我认为你的错误方式是你的NVI模式: http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Non-Virtual_Interface
不确定是否能解决您的问题。
class IFoo
{
public:
void method() { methodImpl(); }
private:
virtual void methodImpl()=0;
};
class FooBase : public IFoo // implement interface IFoo
{
private:
virtual void methodImpl();
};
以下是一个示例,说明为什么您可以使用从XML读取而另一个从DB读取的读取器。请注意,公共结构被移动到NVI readFromSource中,而非常见行为被移动到私有虚拟getRawDatum中。这种方式只需要在一个函数中进行日志记录和错误检查。
class IReader
{
public:
// NVI
Datum readFromSource()
{
Datum datum = getRawDatum();
if( ! datum.isValid() ) throw ReaderError("Unable to get valid datum");
logger::log("Datum Read");
return datum;
}
private:
// Virtual Bits
Datum getRawDatum()=0;
};
class DBReader : public IReader
{
private:
Datum getRawDatum() { ... }
};
class XmlReader : public IReader
{
private:
Datum getRawDatum() { ... }
};
答案 1 :(得分:4)
通常,使用NVI(有时也称为“模板方法”)的原因是派生类只应更改基类行为的部分。所以你要做的就是:
class base {
public:
void f()
{
// do something derived classes shouldn't interfere with
vf();
// do something derived classes shouldn't interfere with
vg();
// do something derived classes shouldn't interfere with
vh();
// do something derived classes shouldn't interfere with
}
private:
virtual void vf(); // might be pure virtual, too
virtual void vg(); // might be pure virtual, too
virtual void vh(); // might be pure virtual, too
};
然后,派生类可以在它们所针对的位置插入f()
并更改f()
行为的方面,而不会弄乱其基本算法。
答案 2 :(得分:2)
可能令人困惑的是,一旦方法在基类中声明为虚拟,它就会在所有派生类中自动变为虚拟,即使在那里不使用virtual
关键字也是如此。因此,在您的示例中,FooBase
的两种方法都是虚拟的。
...拒绝派生类 覆盖共同的可能性 在...中实施的行为 FooBase ::方法()...
如果您可以摆脱IFoo
,只需使用非FooBase
的{{1}}启动层次结构即可。但看起来您希望允许method
的直接子项覆盖IFoo
,但要阻止method()
的子项覆盖它。我不认为这是可能的。
答案 3 :(得分:1)
您可以使用pimpl-idiom来实现这一目标:
class IFoo
{
public:
IFoo( boost::shared_ptr< IFooImpl > pImpl )
: m_pImpl( pImpl )
{}
void method() { m_pImpl->method(); }
void otherMethod() { m_pImpl->otherMethod(); }
private:
boost::shared_ptr< IFooImpl > m_pImpl;
};
class IFooImpl
{
public:
void method();
virtual void otherMethod();
};
现在其他人仍然可以将IFooImpl
作为子类并将其传递给IFoo
,但他们无法覆盖method
的行为(他们可以覆盖otherMethod
)。您甚至可以将IFooImpl
设为IFoo
的直接子类,并使用enable_shared_from_this
正确初始化IFoo
。这只是方法的要点。有很多方法可以调整这种方法。例如,您可以使用factory-pattern确保正确创建IFoo
。
希望有所帮助。