Как реализовать интерфейсный класс, используя не-виртуальную идиому интерфейса в 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
ы созданы правильно.
Надеюсь, это поможет.