Мы оборачиваем объект, реализующий абстрактный класс IFunctionality, в класс, который мы пишем, который также реализует IFunctionality.
Интерфейс IFunctionality определен в стороннем коде и на данный момент состоит только из виртуальных функций, большинство из которых чисто виртуальные. Нечистые виртуальные функции обычно имеют пустую реализацию и переопределяются в конкретной реализации. IFunctionality не имеет переменных-членов (на данный момент).
Наша обертка выглядит так:
class WrapperFunctionality : public IFunctionality
{
public:
WrapperFunctionality(IFunctionality& pOriginal)
: m_pOriginal(pOriginal)
{
}
// We override all virtual functions and forward them to the original.
void doX() override { m_pOriginal->doX(); }
void doY() override { m_pOriginal->doY(); }
// ...
// Well, except a few functions that we specialize.
// That's why we need the wrapper.
void doSomethingSpecial() override { ... }
};
На данный момент все в порядке. Но код может сломаться в будущем.
Проблема 1: сторонний код может добавлять новые нечистые виртуальные функции в IFunctionality. Это останется незамеченным, и мы не сможем вызвать соответствующую функцию в исходном объекте.
Проблема 2: сторонний код может добавлять общедоступные члены в IFunctionality и иметь прямой доступ к этим членам. Мы также не смогли бы передать эти модификации исходным объектам.
Интересно, сможем ли мы обнаружить эти проблемы во время компиляции с помощью static_assert или какой-нибудь магии шаблонов.
Проблема 2 обсуждалась в Как определить, есть ли у класса член переменные? без удовлетворительного решения. Но, на мой взгляд, это маловероятно (поскольку эти сторонние разработчики, похоже, в любом случае не предпочитают общедоступные переменные-члены для этого класса интерфейса).
Но проблема 1, скорее всего, произойдет. Класс интерфейса уже имеет нечистые виртуальные функции и, вероятно, получит новые. Есть ли способ обеспечить, чтобы мой класс реализовывал все виртуальные функции (чистые или нет) из этого класса?
Чтобы прояснить ситуацию, позвольте мне дать более конкретное описание ситуации.
Интерфейс представляет собой поверхность, на которой можно рисовать геометрию с заданными атрибутами:
class ISurface
{
public:
virtual void setColor(const RGB& color) = 0;
virtual void setOpacity(float alpha) = 0;
virtual void drawLine(...) = 0;
virtual void drawCircle(...) = 0;
// etc
};
Конкретная реализация ISurface создается сторонней структурой, и существует несколько ее реализаций, основанных на серверной части.
У нас есть много объектов в нашей системе, которые умеют рисовать себя на ISurface. Мы не контролируем их draw
функцию. Объекты могут быть из сторонних плагинов:
class IDrawableObject
{
public:
virtual void draw(ISurface* pSurface) const = 0;
};
Затем возможная реализация IDrawableObject, возможно, из стороннего кода:
class SomeObject : public IDrawableObject
{
public:
virtual void draw(ISurface* pSurface) const override
{
pSurface->setColor(RGB(255, 0, 0));
pSurface->drawLine(...);
pSurface->setOpacity(0.7f);
pSurface->fillCircle(...);
}
};
В какой-то момент мы хотим изменить визуальное представление этих сторонних объектов, переопределив их цвет или непрозрачность. Мы можем подключиться к процедуре, которая вызывает отрисовку объекта:
// That function will be called by the framework
virtual void drawObjectToSurface(const IDrawableObject* pObject, ISurface* pSurface) override
{
pSurface->setColor(m_overrideColor);
pSurface->setOpacity(m_overrideAlpha);
// Next line does not work as expected, because the object
// overwrites our color and alpha before drawing itself
//pObject->draw(pSurface); // does not work
// Instead, we use a wrapper that blocks color and opacity writes
BlockingSurfaceWrapper wrapperSurface(pSurface);
pObject->draw(&wrapperSurface);
}
ISurface
при сохранении его интерфейса, то для начала следует разделить интерфейс и реализацию. т. е.ISurface
должен только иметь интерфейс (чистые виртуальные функции), а какой-то другой класс, скажем,SimpleSurface
, может предоставить некоторые базовые реализации, которые при желании можно использовать/наследовать. Таким образом, вашBlockingSurfaceWrapper
может просто унаследоватьISurface
при обертыванииSimpleSurface
, и проблема будет решена тривиально. - person Alexander Guyer   schedule 28.01.2021