【问题标题】:Decorating a concrete class, bad practice?装饰一个具体的班级,不好的做法?
【发布时间】:2020-01-07 13:58:23
【问题描述】:

装饰器设计模式的典型uml图如下:https://www.dofactory.com/net/decorator-design-pattern

它们显示装饰器在界面周围。

问题是,它是接口还是具体类真的很重要吗?

首先,装饰器不应该挑剔包装对象的行为,它只是一层功能。所以我看不出这比使用界面有什么风险。

通常我会(喜欢)这样装饰:

class Foo{
    public void doSmth(){
    }
}
class LoggedFoo extends Foo{
    private Foo wrappedFoo;
    public LoggedFoo(Foo foo){wrappedFoo = foo;}
    @override
    public void doSmth(){
        System.out.println("doSmth start");
        wrappedFoo.doSmth();
        System.out.println("doSmth end");
    }
}

我认为我们不应该仅仅看到具体的类继承,因为“它很危险”。 那么,接口还是具体类,在这里重要吗?

【问题讨论】:

  • 永远不要紧——关键是,一如既往,实现接口通常更干净,避免被锁定到具体类。
  • 接口允许一个类实现多个接口,但具体类不允许
  • 好吧,装饰器只扩展一个类
  • 它“很重要”,因为这不是装饰器模式。
  • Michael 你这么说是因为我省略了 Foo 和 LoggedFoo 之间的“抽象装饰器”元素吗?

标签: java decorator


【解决方案1】:

扩展类可能会产生意想不到的副作用。

如果 Foo 已经有一个构造函数(或多个),你也必须实现它们并调用超类。

此外,如果 Foo 在构造函数中具有私有状态或副作用,它们现在会被执行两次。一次用于在构造函数中传递的 Foo,一次用于 LoggedFoo,它也是一个 Foo。

它可能会变得混乱并导致错误和增加资源消耗,但这完全取决于类。如果类表现得像一个接口(默认构造函数,没有副作用,没有状态),它可能不会直接成为问题。但此时它只是一个调用另一个实例的子类,这可能是一个方便的构造,但我不会使用装饰器模式来调用它。

【讨论】:

  • 这是一个有趣的方面
  • @Sheed 这个类现在包装一个 Foo,我们称它为 foo1,它本身是一个 Foo:foo2。因此,当您使用此类时,至少存在两个 Foo。装饰界面没有这个效果。
  • 是的,在意识到私有成员仍然可以存在并在我不调用构造函数的情况下被初始化后,我删除了我的评论。
【解决方案2】:

这很重要,因为Decorators 的整个想法是在不走实现继承路线的情况下添加功能。如果您可以接受实现继承并冒着改变对象现有行为的风险,那么根本不需要装饰器。

整个想法是在不破坏实现继承所做的现有行为的情况下扩展功能。

【讨论】:

  • 类的契约写在继承层次结构的最高层,无论顶层父级是接口还是类。如果父级是一个接口,这个契约也可能被破坏。
【解决方案3】:

您应该使用接口,因为这是更好的做法。它们更容易测试和更改实现。如果您想在生产环境中使用不同类型的记录器(数据库、文件、控制台等)怎么办?

【讨论】:

  • 如果是不同的记录器,我会用同样的方式装饰,一个 LoggedFoo 的实例可以用另一个装饰器来装饰。
猜你喜欢
  • 2021-03-17
  • 2010-10-15
  • 2018-09-07
  • 2021-05-19
  • 2015-08-13
  • 2016-01-13
  • 2014-06-07
  • 2020-10-20
  • 1970-01-01
相关资源
最近更新 更多