如何在C ++中使用非虚拟接口惯用法实现接口类?

时间:2010-04-29 07:49:11

标签: c++ interface idioms non-virtual-interface

在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)。

4 个答案:

答案 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

希望有所帮助。