2010-04-29 5 views
3

В 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) идиома OUGHT запретить производным классам возможность переопределения общего поведения, реализованного в FooBase::method(), но поскольку IFoo сделал это виртуальным, кажется, что все производные классы имеют возможность переопределить FooBase::method().

Если я хочу использовать идиому NVI, каковы мои варианты, отличные от уже упомянутой идиомы pImpl (спасибо space-c0wb0y).

ответ

4

Как правило, причиной использования NVI (иногда также называемого «Template Method») является то, что производные классы должны только изменять часть поведения базового класса. Так что вы делаете это:

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() «s, не портя ее основной алгоритм.

+0

Совершенно верно, но как я могу получить базовый класс для реализации интерфейса без потери гарантий (например, порядка выполнения vf, vg и vh), который предлагает мой метод шаблонов? –

+0

@andreas: Я ответил на ваш комментарий к ответу Майкла. Я предлагаю вам продлить ваш вопрос, чтобы он немного описал, что вы хотите сделать. Из того, что я понимаю, возможно, смешанные или политики могут быть тем, что вы ищете. – sbi

+1

NVI и Template Method - совершенно разные концепции; хотя NVI - это то, как обычно применяется метод шаблонов. Вы могли бы просто использовать шаблонный метод, который использует объект стратегии для своих виртуальных машин, и вы можете просто иметь шаблон NVI, который не соответствует шаблону метода шаблона. –

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, а другой - из БД. Обратите внимание, что общая структура перемещается в 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() { ... } 
}; 
+0

Я хотел бы работать с классом интерфейса, который * только * предписывает методы, предлагаемые любой реализацией этого интерфейса, и базовый класс, который гарантирует некоторую общность в поведении его производных классов с использованием идиомы NVI (так называемого метода шаблона) , Я мог бы иметь два совершенно разных базовых класса: один, который взаимодействует с XML-файлом, и тот, который взаимодействует с RDBMS, например. Они реализуют IFoo и должны гарантировать некоторую общность в поведении производных классов. –

+0

@andreas: В C++ интерфейсы описываются с помощью (возможно, абстрактных) базовых классов, которые объявляют (возможно, чистые) виртуальные функции. Производные классы затем реализуют эти виртуальные функции, переопределяя их. Любая виртуальная функция-член базового класса может быть переопределена в _any_ производных классах. Поэтому NVI может быть реализован только в классе _base_ путем реализации методов _non-virtual_. Если вам нужен двухслойный подход, простой NVI не будет работать, поскольку классы, полученные из общих базовых классов (которые вытекают из интерфейса), могут переопределить все виртуальные функции интерфейса. – sbi

+0

@andreas Я добавил пример, который делает то, что вы описываете в своем комментарии. Надеюсь, это имеет смысл. Я думаю, вы хотите больше, чем это, но .. но я не уверен, что именно. Возможно, вам следует продолжить свой вопрос с полным примером 1), что вы хотели бы сделать, и/или 2) Что происходит с обычным NVI? –

1

Вы можете использовать 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. Это всего лишь суть метода. Существует много способов подбора этого подхода. Например, вы можете использовать factory-pattern, чтобы убедиться, что IFoo s созданы правильно.

Надеюсь, что помогает.

+0

Хм .. это кажется большим количеством накладных расходов и механизмов, чтобы избежать частной виртуальной функции .... –

+0

Проблема в том, что любая виртуальная функция, даже частные, может быть переопределена подклассами. Этот вариант имеет много шаблонов, правда, но он пуленепробиваемый. Так что это компромисс. –

2

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

... отрицать производных классов в возможность перекрывая общий поведение реализуется в FooBase ::() метод ...

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

 Смежные вопросы

  • Нет связанных вопросов^_^