Как реализовать интерфейсный класс, используя не-виртуальную идиому интерфейса в C++?

В 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 ответа

Я думаю, что ваш паттерн 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, а другой из БД. Обратите внимание, что общая структура перемещается в readFromSource NVI, а нестандартное поведение перемещается в частный виртуальный 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() { ... }
};

Обычно причина использования 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()Поведение, не портя свой фундаментальный алгоритм.

Может возникнуть путаница, что, как только метод объявлен как виртуальный в базовом классе, он автоматически становится виртуальным во всех производных классах, даже если virtual ключевые слова там не используются. Так что в вашем примере оба метода FooBase являются виртуальными.

... чтобы запретить производным классам возможность переопределения общего поведения, реализованного в FooBase::method()...

Если вы можете избавиться от IFooи просто начать иерархию с FooBase с не виртуальным method, это сделало бы это. Но похоже, что вы хотите разрешить прямым детям IFoo переопределить method(), но для предотвращения детей FooBase переопределить это. Я не думаю, что это возможно.

Вы можете использовать pimpl-идиому для достижения этой цели:

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 правильно. Это только суть метода. Есть много способов настроить этот подход. Например, вы можете использовать фабричный шаблон, чтобы убедиться, IFoo ы созданы правильно.

Надеюсь, это поможет.

Другие вопросы по тегам