我在许多与装饰器模式相关的UML类图中看到,抽象装饰器既包含抽象组件又同时扩展了抽象类组件。
例如,在以下链接中与咖啡制作方案相关的第二个示例中:
类CoffeeDecorator
扩展了抽象组件Coffee
,并包含Coffee
类型的字段。这是为什么?这只是一个案例,还是我们应该总是如何构建“装饰系统”,为什么?
我认为只有CoffeeDecorator
包含要装饰的组件才有必要,因为CoffeeDecorator
实际上不是Coffee
。
答案 0 :(得分:2)
可以使用包装对象("是")。这就是它必须扩展/实现类/接口的原因。
装饰器/包装器也有一个它要装饰/包装的实例的引用("有一个")。
正如您问题中的链接示例所示,您可以写下:
Coffee coffee = new WithMilk(new SimpleCoffee());
其中WithMilk
是装饰者。
答案 1 :(得分:0)
Decorator和装饰类实现相同的接口。这允许decorator类为修饰类的每个方法(在接口中声明)添加额外的功能。这就是包装/装饰的意思。
public class SimpleCoffee extends Coffee {
@Override
public double getCost() {
return 1;
}
@Override
public String getIngredients() {
return "Coffee";
}
}
public abstract class CoffeeDecorator extends Coffee {
protected final Coffee decoratedCoffee;
public CoffeeDecorator(Coffee c) {
this.decoratedCoffee = c;
}
public double getCost() { // Implementing methods of the abstract class
//you can add extra functionality here.
return decoratedCoffee.getCost();
}
public String getIngredients() {
//you can add extra functionality here.
return decoratedCoffee.getIngredients();
}
}
装饰者类可以由更大的装饰者装饰。
public class BiggerDecorator extends Coffee {
protected final Coffee decoratedCoffee;
public CoffeeDecorator(Coffee c) {
this.decoratedCoffee = c;
}
public double getCost() { // Implementing methods of the abstract class
return decoratedCoffee.getCost();
}
public String getIngredients() {
return decoratedCoffee.getIngredients();
}
}
您现在可以执行以下操作。 Java IO类属于这种结构。
Coffee coffeeDecorator=new CoffeeDecorator(new Coffee);
Coffee biggerDecorator=new BiggerDecorator(coffeeDecorator);
为了多态,decorator类必须实现该接口。
答案 2 :(得分:0)
你的问题对我来说似乎有些循环......实现一个界面并拥有一个界面的实例,即#34;装饰"是装饰器模式的定义。
所以你的问题有点像询问"为什么装饰器模式使用装饰器模式?"
如果你要包装的东西和你自己之间没有共同界面,那么它只是一种关系,或者,如果你正在调整这个东西的界面你正在包装到其他一些界面,它可能是适配器模式。
我认为你真正的问题是"为什么装饰者模式有用?是什么使得实现与你包装的东西相同的界面是有用的?"
装饰器模式启用相同行为的变体
装饰器模式的最大价值来自于你有一些系统必须处理几个微妙不同的东西,但想要不必知道复杂性,并能够以同样的方式对待它们。当然,这通常是我们从界面获得的。但在某些情况下,同一界面的众多实现并不是最佳解决方案......在经典示例中,假设您尝试为星巴克所服务的每种咖啡类型编写单独的类。
您将从基础开始,但逐渐发现自己需要编写像VentiMochaChaiWhipWithCinnamonOnTop
这样的课程。每次添加新的风味成分时,您都需要编写几十个新类,每次删除一个类,几十个类就会过时。
另一方面,使用装饰器模式,可以为每种饮料提供一个单独的类,并使软件系统的大部分不必处理各种饮料类型;他们可以通过Drink
界面对每种饮料进行相同处理(无论它有多少个附件)。
当结账时的人点击Chai按钮时你只需要new Chai()
,并且因为其他属性可以添加到任意饮料中,你只需用new Venti(drink)
,{{1 }},new Mocha(drink)
,new Whip(drink)
等等,当客户请求加载项时,注册表中的人员会点击按钮。
装饰器模式的限制
这种模式有其局限性,因为如果你只是为每种调味品使用了一套布尔值,那么对你所饮用的饮料的反省有点困难。我很确定在为星巴克设计收银机时跟踪饮料类型实际上并不是很有用,因为它不会非常善于处理"哎呀,我的意思是黑莓风味,而不是覆盆子" ...支持装饰模式需要通过几个嵌套的Drink装饰器进行内省,以找到需要移除的new WithCinnamon(drink)
装饰器。
它也不是非常善于处理具有复杂关系的事物......如果鞭打的打顶需要知道与之相关的基础饮料类型,以了解它是否应该添加$。无论价格与否,那么装饰者模式将非常有用:装饰模式适用于各种装饰品不必知道它们包装的具体实施方式。当我们可以使用它时,它有助于减少耦合。
我真正认为有用
我认为我们使用饮料示例来解释装饰器模式主要是因为它很容易谈论需要创建的类数量的潜在指数节省,而许多真实的例子在哪里#&# 39;对装饰某些东西很有用,但实际上并没有使用装饰模式的属性。
我可以想到在我们公司使用了与模式有关的地方的案例,我们希望在流程中添加一些日志记录。例如,通过修改Eclipse IProgressMonitor,我们可以在每次显示的任务或子任务名称更改时进行检测,并记录该过程的每个步骤所花费的时间,因此我们知道哪些步骤花费的时间最多。
作为装饰器执行此操作使我们能够访问所有日志记录信息,而无需更改现有代码(只需要监视作业的启动),因为它们都知道如何处理IProgressMonitor已经,我们不必知道我们内部包含的进度监视器内部是什么:我们不必提供替代实现,只需用我们想要的其他行为包装现有实现。
答案 3 :(得分:0)
GOF书中的装饰器模式介绍中提到了答案。
这是报价:
“装饰器符合其装饰组件的接口 因此它的存在对组件的客户端是透明的。 透明度可让您递归嵌套装饰器,从而允许 无限数量的附加职责”。</ p>
因此,我认为客户端不必担心两个不同的接口,一个用于装饰器,另一个用于组件。另一个原因是Puce的答案。