“ 动态地将附加职责附加到对象。装饰器为扩展功能提供了一种灵活的子类化替代方案 。”
装饰者模式:介绍
我们学习的经典 四人组 结构模式的共同目标—— 适配器 、 桥接器 和 组合器 是确定如何以简单灵活的方式实现类和对象之间的复杂关系。装饰器模式也有相同的目标,但解决的是不同的问题领域。
在考虑扩展功能时,新程序员可能想到的第一件事就是继承。然而,继承可能不是所有情况下的理想解决方案。当您通过子类继承功能时,功能是在编译时静态设置的,它并不总是导致最灵活或可维护的设计。如果需要增加新的功能,就需要修改代码,这是违反 开闭原则 的。
相反,您可以动态地将新职责附加到对象。这正是装饰者模式的预期用途。根据四人帮的说法——“ 动态地为一个对象附加额外的职责 ”。但是你是怎么做到的呢?通过使用组合。通过组合,您可以在运行时动态地向对象添加多个新职责。这里的好处是对象不需要意识到它,你的设计符合 Open Closed Principle 。我们常常期待 Java 等伟大的面向对象编程语言的优雅,而忽略了使用组合来解决问题的简单性。
让我们来看一个问题场景,看看装饰者模式是如何解决它的。
装饰者模式的参与者
考虑一个卖鲜花花束的花商,例如玫瑰花束、兰花花束等。当顾客走进来时,花店会描述花束并告知其价格。我们可以使用 FlowerBouquet 抽象基类和特定花束的子类对需求进行建模。
顾客除了花束外,还可以要求花店用纸包装、丝带蝴蝶结和闪粉装饰,花店会额外收费。为了满足新的需求,我们可以添加 FlowerBouquet 的新子类,每个子类代表一束带有额外装饰的花束,这就是我们现在的设计。
我们所拥有的是“ 阶级爆炸 ”,而且在花束中只添加一种装饰也是如此。想象一下用多种装饰品来表示一束花,例如同时带有丝带蝴蝶结和闪光的玫瑰花束,或者带有纸包、丝带蝴蝶结和闪光的兰花花束。更多注意事项:
- 如果我们想要一束双层纸包装的玫瑰花束怎么办?
- 如果我们想添加一个新的百合花束怎么办?
- 如果我们想添加新的观赏树叶装饰怎么办?
显然我们的设计是有缺陷的。但这是装饰器模式的理想用例。通过使用装饰器模式,我们可以创建一束花并在运行时用任意数量的特征对其进行动态装饰。花束在不知不觉中“ 被装饰 ”了。为此,我们将创建一个抽象基类 FlowerBouquet 和特定的子类 RoseBouquet 和 OrchidBouquet 以从它扩展。我们还将创建一个抽象的 FlowerBouquetDecorator 类来扩展 FlowerBouquet 。对于每个装饰器,我们将创建 Glitter 、 PaperWrapper 和 RibbonBow 类以从 FlowerBouquetDecorator 扩展。这就是设计的方式。
在上图中,最重要的是观察 FlowerBouquet 和 FlowerBouquetDecorator 之间的关系。我们有两种类型的关系:
- 通过从 FlowerBouquet 继承 FlowerBouquetDecorator 以获得正确的类型。
- 通过组合 FlowerBouquetDecorator 和 FlowerBouquet 来组合以添加新行为。
现在让我们看看我们的类如何映射到装饰器模式的参与者:
- Component ( FlowerBouquet ):是一个抽象基类,可以动态装饰职责。
- ConcreteComponent ( RoseBouquet 和 OrchidBouquet ):是扩展 Component 以表示可以附加附加职责的对象的具体类。
- Decorator ( FlowerBouquetDecorator ):是一个抽象类,扩展了 Component ,作为具体装饰器类的基类。
- ConcreteDecorator( PapperWrapper 、 RibbonBow 和 Glitter ):是扩展 Decorator 的 具体类,用于装饰具有职责的 组件 。
应用装饰器模式
为了将装饰器模式应用到花束示例中,让我们编写抽象基类—— Component 。
花束.java
package guru.springframework.gof.decorator.components;
public abstract class FlowerBouquet {
String description;
public String getDescription() {
return description;
}
public abstract double cost();
}
在上面的
FlowerBouquet
类中,我们编写了一个
description
实例变量,一个返回它的
getDescription()
方法,以及一个抽象的
cost()
方法。
接下来我们将编写 ConcreteComponent 类。
玫瑰花束.java
package guru.springframework.gof.decorator.components;
public abstract class FlowerBouquet {
String description;
public String getDescription() {
return description;
}
public abstract double cost();
}
兰花花束.java
package guru.springframework.gof.decorator.components;
public abstract class FlowerBouquet {
String description;
public String getDescription() {
return description;
}
public abstract double cost();
}
我们从
FlowerBouquet
扩展了上面的两个类,并在它们的构造函数中初始化了
description
变量。我们还在每个类中实现了
cost()
方法以返回它们的成本。
现在我们已经编写了组件,我们将编写 装饰器 。
FlowerBouquetDecorator.java
package guru.springframework.gof.decorator.components;
public abstract class FlowerBouquet {
String description;
public String getDescription() {
return description;
}
public abstract double cost();
}
我们从
FlowerBouquet
扩展了
FlowerDecorator
类并声明了一个抽象的
getDescription()
方法。我们这样做是因为我们希望我们所有的花卉装饰器类都实现
getDescription()
以包括装饰器的描述和它正在装饰的花束。一旦我们编写了
ConcreteDecorator
类,这将变得更加清晰。让我们从
Glitter
开始。
闪光.java
package guru.springframework.gof.decorator.components;
public abstract class FlowerBouquet {
String description;
public String getDescription() {
return description;
}
public abstract double cost();
}
我们从
FlowerDecorator
扩展了
Glitter
类,并添加了一个
FlowerBouquet
实例变量 (
Composition
) 来保存我们正在包装的花束。实例变量在运行时通过构造函数调用进行初始化。然后我们实现了
getDescription()
方法并返回了这个装饰器的描述和它正在装饰的花束。我们还实现了
cost()
方法,将这个装饰器的成本添加到它正在装饰的花束的成本中。
我们将类似地编写其他装饰器类。
PaperWrapper.java
package guru.springframework.gof.decorator.components;
public abstract class FlowerBouquet {
String description;
public String getDescription() {
return description;
}
public abstract double cost();
}
丝带弓.java
package guru.springframework.gof.decorator.components;
public abstract class FlowerBouquet {
String description;
public String getDescription() {
return description;
}
public abstract double cost();
}
现在让我们编写一些测试代码,看看装饰器模式在运行时是如何工作的。
FlowerBouquetTest.java
package guru.springframework.gof.decorator.components;
public abstract class FlowerBouquet {
String description;
public String getDescription() {
return description;
}
public abstract double cost();
}
在上面的测试类的第 13-15 行中,我们首先实例化了一个
RoseBouquet
并打印了它的描述和成本。在第 18 行到第 23 行中,我们再次实例化了一个
RoseBouquet
,这次我们在打印其描述和成本之前用
PaperWrapper
、
RibbonBow
和
Glitter
装饰器包装它。在第 26 行到第 31 行中,我们实例化了一个
OrchidBouquet
,并用
PaperWrapper
将其包装两次,然后在打印其描述和成本之前用
RibbonBow
包装一次。
上面代码的输出是这样的。
package guru.springframework.gof.decorator.components;
public abstract class FlowerBouquet {
String description;
public String getDescription() {
return description;
}
public abstract double cost();
}
结论
装饰器模式是无需子类化即可动态扩展行为的绝佳解决方案,但它通常会导致出现大量装饰器类。虽然创建类和应用模式并不复杂,但对于新程序员来说,弄清楚并使用现有的基于装饰器的代码有时会让人不知所措。 java.io API 主要基于装饰器模式,既然您已经了解了它,您将可以轻松地使用装饰器类(例如 BufferedInputStream 和 DataInputStream) 包装输入和输出流。此外,如果您觉得客户可能会发现您的基于装饰器的代码很复杂,您始终可以使用 工厂方法 、 抽象工厂 或 生成器 创建模式来封装复杂性。
装饰器模式也在使用 Spring 框架进行 企业应用程序开发 期间使用。您将经常需要执行任务,例如事务、缓存同步和与安全相关的任务。如果您不想冒险弄乱业务代码,请使用装饰器。通常,使用装饰器模式会使您的代码更简洁、更易于维护。