4

在 C++ 中,接口可以由其方法是纯虚拟的类来实现。

这样的类可以是库的一部分,用于描述对象应该实现哪些方法才能与库中的其他类一起工作:

class Lib::IFoo
{
    public:
        virtual void method() = 0;
};

class Lib::Bar
{
    public:
        void stuff( Lib::IFoo & );
};

现在我想使用 class Lib::Bar,所以我必须实现IFoo接口。

出于我的目的,我需要一个完整的相关类,所以我想使用一个保证使用 NVI 习语的常见行为的基类:

class FooBase : public IFoo // implement interface IFoo
{
    public:
        void method(); // calls methodImpl;

    private:
        virtual void methodImpl();
};

FooBase::method()非虚拟接口 (NVI )习惯用法应该拒绝派生类IFoo覆盖FooBase::method().

如果我想使用 NVI 成语,除了已经建议的 pImpl 成语之外,我还有哪些选择(感谢 space-c0wb0y)。

4

4 回答 4

6

我认为你的 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() { ... }
};
于 2010-04-29T07:57:15.957 回答
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(),而不会弄乱它的基本算法。

于 2010-04-29T07:56:42.840 回答
2

令人困惑的是,一旦一个方法在基类中声明为 virtual,它在所有派生类中都会自动变为 virtual,即使virtual那里没有使用关键字。所以在你的例子中,这两种方法FooBase都是虚拟的。

...拒绝派生类覆盖 FooBase::method() 中实现的常见行为的可能性...

如果您可以摆脱IFoo,并且只需FooBase使用 non-virtual开始层次结构method,那就可以了。但看起来您希望允许的直接子代IFoo覆盖method(),但防止子代FooBase覆盖它。我不认为这是可能的。

于 2010-04-29T09:25:13.810 回答
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。这只是该方法的要点。有很多方法可以调整这种方法。例如,您可以使用工厂模式来确保IFoos 被正确创建。

希望有帮助。

于 2010-04-29T08:53:36.970 回答