【问题标题】:Decorator for class with non-virtual members具有非虚拟成员的类的装饰器
【发布时间】:2020-05-01 23:10:48
【问题描述】:

我正在尝试将装饰器模式用于通常的目的,以便能够向我的类添加功能,同时保持对类层次结构的控制。我的困难源于我的类A 有一个成员basicVar 和一个方法basicOp(),它处理一些未指定的基本功能,这些功能是必需的,但在任何派生类中都不会改变。因此,我将 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();}
}

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 中存在一个数据成员,或者实际上装饰器模式仅用于所有方法都声明为虚拟的类?

提前致谢。

【问题讨论】:

  • 开销是如此微不足道,如果它解决了另一个问题,那么不使用它是没有意义的。
  • @MichaelChourdakis 感谢您的回答。我同意你的观点,但在我看来,将其设为虚拟可能会导致一些用户认为它应该在派生自 A 的类中被覆盖,而唯一打算这样做的类是装饰器。对我来说,这听起来像是一个糟糕的设计。
  • 由于basicOp 不是抽象的,因此可以解释为可以重写该函数,但不必如此。连同良好的文档(“此功能不应被覆盖”),您只需要相信A 的用户不会做任何坏事。如果他们这样做并抱怨,那么你可以说“嘿,我告诉过你这不是要被推翻的,你只能怪你自己!” :)
  • @Someprogrammerdude 是的,我同意这是可行的,并且可能是要走的路。尽管如此,它闻起来并不是 100% 好的,所以在继续之前,我想了解我是否由于糟糕的设计选择而陷入了一个已知的陷阱。
  • @NicolaMori 我认为您不能也不应该解决这个问题。您实际上希望 basicOp 具有虚拟行为,因此只需将其声明为虚拟 ;)

标签: c++ decorator


【解决方案1】:

你是对的,A有状态的 与装饰器的使用不兼容。你有一个矛盾:你说basicOp 永远不会在派生类中更改,但是你有一个合理的派生类ADecorator 确实想要更改它(通过将其转发给另一个目的)。如果basicOp 可以在没有任何成员变量的情况下实现(例如,它只是一些虚拟调用的包装器),那么内部或外部对象执行对它的调用就没有问题。

解决此问题的一种方法是将A 的那部分分隔ConcreteA 中并在A 中提供

virtual ConcreteA& getConcrete()=0;

然后在装饰器中存储unique_ptr&lt;ConcreteA&gt;,并适当实现getConcrete;任何使用ConcreteA直接的人都可以拨打非虚拟电话。

【讨论】:

  • 非常感谢,我也有类似的想法,但由于我有限的编程技能,他们非常困惑。现在我想我明白了,我会在我的真实代码上尝试你的建议,看看它是否符合我的需求,顺便说一句,很遗憾这样一个通用规则(“装饰器不适合与有状态类一起使用” ) 没有出现在我读过的任何装饰器教程中,它会为我节省很多时间。
【解决方案2】:

受 Davis Herring 提议的启发,我将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-&gt;basicOp() 对装饰器和被装饰对象都起作用。

这看起来很 hack,因为它使派生类可以使用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;
}

我不知道这个实现是否存在任何基本问题(除了明显的复杂性),但现在它对我有用。

【讨论】:

    猜你喜欢
    • 2012-09-27
    • 2020-05-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-14
    • 2013-09-12
    • 2021-12-17
    • 2016-08-13
    相关资源
    最近更新 更多