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

В 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 ]

5 голосов
/ 29 апреля 2010

Я думаю, что у вас неправильная схема паттернов 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() { ... }
};
4 голосов
/ 29 апреля 2010

Обычно причина использования 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 голосов
/ 29 апреля 2010

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

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

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

1 голос
/ 29 апреля 2010

Вы можете использовать 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 s созданы правильно.

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

...