设计模型|装饰模式

Yeren Lv3

定义

装饰模式(Decorator Pattern) :动态地给一个对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰模式比生成子类实现更为灵活。其别名也可以称为包装器(Wrapper),与适配器模式的别名相同,但它们适用于不同的场合。根据翻译的不同,装饰模式也有人称之为“油漆工模式”,它是一种对象结构型模式。

动机

一般有两种方式可以实现给一个类或对象增加行为:

  • 继承机制,使用继承机制是给现有类添加功能的一种有效途径,通过继承一个现有类可以使得子类在拥有自身方法的同时还拥有父类的方法。但是这种方法是静态的,用户不能控制增加行为的方式和时机。
  • 关联机制,即将一个类的对象嵌入另一个对象中,由另一个对象来决定是否调用嵌入对象的行为以便扩展自己的行为,我们称这个嵌入的对象为装饰器(Decorator)

装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任,换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。这就是装饰模式的模式动机。

设计类图

结构

  • Component: 抽象构件
  • ConcreteComponent: 具体构件
  • Decorator: 抽象装饰类
  • ConcreteDecorator: 具体装饰类

样例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package DesignPattern;

interface Component{
void operation();
}

class ConcreteComponent implements Component{

@Override
public void operation() {
System.out.println("this is a concrete component.");
}
}

public class Decorator implements Component{
private Component component;
public Decorator(Component c){
component = c;
}
@Override
public void operation() {
System.out.println("this is a decorator.");
component.operation();
}
}

class ConcreteDecoratorA extends Decorator{

private String addedState = "Concrete Decorator A";

public ConcreteDecoratorA(Component c) {
super(c);
}

public void operation(){
super.operation();
System.out.println(addedState);
}
}

class ConcreteDecoratorB extends Decorator{

public ConcreteDecoratorB(Component c) {
super(c);
}

public void addedBehavior(){
System.out.println("add behavior by concrete decorator B");
}

public void operation(){
super.operation();
addedBehavior();
}
}

class Main{
public static void main(String[] args) {
Component base = new ConcreteComponent();
Component decoratorA = new ConcreteDecoratorA(base);
Component decoratorB = new ConcreteDecoratorB(decoratorA);
decoratorB.operation();
}
}

总结分析

  • 与继承关系相比,关联关系的主要优势在于不会破坏类的封装性,而且继承是一种耦合度较大的静态关系,无法在程序运行时动态扩展。在软件开发阶段,关联关系虽然不会比继承关系减少编码量,但是到了软件维护阶段,由于关联关系使系统具有较好的松耦合性,因此使得系统更加容易维护。当然,关联关系的缺点是比继承关系要创建更多的对象。
  • 使用装饰模式来实现扩展比继承更加灵活,它以对客户透明的方式动态地给一个对象附加更多的责任。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。

优点

  • 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
  • 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的装饰器,从而实现不同的行为。
  • 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
  • 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”

缺点

  • 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,同时还将产生很多具体装饰类。这些装饰类和小对象的产生将增加系统的复杂度,加大学习与理解的难度。
  • 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。

例题

假设你正在开发一个咖啡店的点单系统,该系统需要支持以下几种类型的咖啡:

  1. 美式咖啡(American Coffee):简单的黑咖啡。
  2. 拿铁咖啡(Latte):在美式咖啡的基础上加入一定比例的牛奶和奶泡。
  3. 卡布奇诺(Cappuccino):在拿铁咖啡的基础上再加入一定比例的巧克力粉。

每种咖啡都有不同的价格,而且用户还可以选择额外添加以下配料:

  1. 牛奶(Milk):为指定的咖啡添加牛奶,增加一定价格。
  2. 奶泡(Foam):为指定的咖啡添加奶泡,增加一定价格。
  3. 巧克力粉(Chocolate Powder):为指定的咖啡添加巧克力粉,增加一定价格。

请使用装饰模式设计并实现一个该系统的咖啡订单功能,使得用户可以根据自己的喜好选择不同的咖啡类型和配料,并能正确计算订单的总价格。

设计类图

参考代码

提示(思路一)

  1. 定义一个咖啡接口(Coffee),包含获取咖啡描述和获取咖啡价格的方法。
  2. 创建具体的咖啡类,分别表示美式咖啡、拿铁咖啡和卡布奇诺,实现咖啡接口中的方法。
  3. 创建装饰者抽象类(CoffeeDecorator),继承自咖啡接口,并在内部保存一个咖啡对象的引用。
  4. 创建具体的装饰者类,分别表示牛奶、奶泡和巧克力粉,通过扩展装饰者抽象类来实现特定的装饰功能。
  5. 在客户端中使用装饰者模式,创建指定类型的咖啡对象,并根据需要添加配料装饰者。最后调用咖啡对象的方法获取订单描述和总价格。

(思路二)

  1. 定义一个原料接口(Ingredients),包含原料的描述和获取原料价格的方法。
  2. 创建具体的咖啡类(美式咖啡),实现原料接口的方法。
  3. 创建装饰者抽象类(CoffeeDecorator),继承自原料接口,并在内部保存一个咖啡对象的引用。
  4. 创建具体的装饰者类,分别表示牛奶、奶泡和巧克力粉,通过扩展装饰者抽象类来实现特定的装饰功能。
  5. 在客户端中使用装饰者模式,创建咖啡对象,并根据需要添加配料装饰者。最后调用咖啡对象的方法获取订单描述和总价格。
  • Title: 设计模型|装饰模式
  • Author: Yeren
  • Created at : 2023-08-22 00:00:00
  • Updated at : 2023-08-22 00:00:00
  • Link: https://blog.yeren.xyz/2023/08/22/DP-decorator-pattern/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments