Декоратор для класса с не виртуальными участниками - PullRequest
4 голосов
/ 15 января 2020

Я пытаюсь использовать шаблон декоратора для обычной цели, чтобы иметь возможность добавлять функциональные возможности в мой класс, сохраняя при этом контроль над иерархией классов. Моя трудность связана с тем фактом, что в моем классе A есть член basicVar и метод basicOp(), который имеет дело с некоторыми неуказанными базовыми функциями c, которые необходимы, но никогда не будут изменены в любом производном классе. По этой причине я объявляю A как:

class A {
public:
  virtual void func() { /* Some default implementation */}
  void basicOp() { /* some basic operation on basicVar*/}
private:
  int basicVar;
}

Таким образом, производные классы не должны реализовывать basicOp(), а вызовы basicOp() не связаны с накладными расходами виртуального вызова. Затем я реализую базовый класс декоратора следующим образом:

class ADecorator: public A{
protected:
  std::unique_ptr<A> _a;

public:
  ADecorator(std::unique_ptr<A> a): _a(std::move(a)){}
  void func(){ _a->func(); }
  void basicOp(){ _a->basicOp();}
}

Спецификаторы c, наследующие от ADecorator, будут переопределять func() для обеспечения дополнительного поведения. Теперь возникает моя проблема: при использовании декоратора, унаследованного от ADecorator с использованием интерфейса A, подобного следующему:

std::unique_ptr<A> dec = std::make_unique<Decorator>(std::make_unique<A>());
dec->basicOp();

, вызываемый метод будет A::basicOp(), который работает на dec.basicVar вместо ADecorator::basicOp() который действует на элемент dec._a.basicVar объекта, обернутого декоратором. Это не произойдет с func(), поскольку он виртуальный. Объявляя также basicOp() как виртуальный, проблема решается, но объявление метода виртуальным просто для того, чтобы сделать возможным использование декораторов, звучит как привинчивание интерфейса.

Я совершенно уверен, что это должно проистекать из дизайна ошибка, но я не могу точно понять, какой и как ее решить. Может быть, проблема заключается в наличии члена данных в A или в том факте, что эффективно шаблон декоратора предназначен для использования только с классами со всеми методами, объявленными как виртуальные?

Заранее спасибо.

Ответы [ 2 ]

0 голосов
/ 13 февраля 2020

Вдохновленный предложением Дэвиса Херринга, я переделал A как:

class A {
public:
  A(): repr{std::make_shared<Representation>()} {}
  virtual void func() { /* Some default implementation */}
  void basicOp() { /* some basic operation on repr->basicVar*/}
protected:
  struct Representation{
    int basicVar;
  }
  A(const std::shared_ptr<Representation> &extRepr){repr = extRepr;};
  std::shared_ptr<Representation> Repr() {return repr;};
private:
  std::shared_ptr<Representation> repr;
}

и ADecorator как:

class ADecorator: public A{
protected:
  std::unique_ptr<A> _a;

public:
  ADecorator(std::unique_ptr<A> a): A(a->Repr()), _a(std::move(a)){}
  void func(){ _a->func(); }
}

Таким образом, и декоратор, и декорированный объект разделяет одно и то же представление A, и, таким образом, dec->basicOp() работает как для декоратора, так и для декорированного объекта.

Это выглядит довольно странно, поскольку делает представление A доступным для производных классов, поэтому A не может иметь по-настоящему частных членов. Лучшая версия могла бы быть:

class A {
public:
  A(): repr{std::make_shared<Representation>()} {}
  virtual void func() { /* Some default implementation */}
  void basicOp() { /* some basic operation on repr->basicVar*/ }
protected:
  class Representation{
    friend class A;
    int basicVar;
  }
  A(const std::shared_ptr<Representation> &extRepr){repr = extRepr;};
  std::shared_ptr<Representation> Repr() {return repr;};
private:
  std::shared_ptr<Representation> repr;
}

Я не знаю, есть ли какая-либо фундаментальная проблема с этой реализацией (кроме заметной сложности), но пока она работает для меня.

0 голосов
/ 16 января 2020

Вы правы, что A, будучи с состоянием , несовместимо с использованием декоратора. У вас есть противоречие: вы сказали, что basicOp никогда не будет изменено в производном классе, и все же у вас есть разумный производный класс ADecorator, который хочет изменить (переадресовав его другому объекту). ). Если бы basicOp мог быть реализован без каких-либо переменных-членов ( например , это была просто оболочка для некоторых виртуальных вызовов), не было бы проблем с внутренним или внешним объектом, выполняющим вызовы к нему.

Один из способов решить эту проблему - разделить эту часть A на ConcreteA и предоставить в A

virtual ConcreteA& getConcrete()=0;

Затем сохранить unique_ptr<ConcreteA> в декоратор и реализовать getConcrete соответственно; любой, кто использует ConcreteA напрямую , может совершить не виртуальный вызов.

...