GoF设计模式(六):Decorator Pattern 装饰器模式

继承是拓展类功能最常见的手段,但是其缺点也很明显,其耦合程度较高。这里我们介绍一种新的方法来拓展类的功能——Decorator Pattern装饰器模式,其是结构性模式的一种,通过包装的方式实现动态增强、拓展已有对象的功能

abstract.jpeg

简介

在 Decorator Pattern 装饰器模式下,如果我们期望拓展已有对象的功能,那么就可以通过装饰者对象来包装该这个已有对象来实现。显然这种动态的方式比继承会更加灵活。具体地,装饰者对象内部会持有被装饰对象的引用,这样装饰者对象一方面用于实现拓展的功能,另一方面其会再将请求转发给被装饰的对象。在Java的IO流中就大量使用了该模式

这里我们以肯德基的Pizza为例来进行引入,对于一个KFCPizza对象来说,其具备的基本功能就是烹饪一个乞丐版的Pizza。但实际上不同口味的客户可能需要添加不同的配料。比如有人要加牛肉,有人要加榴莲,还有人要加水果。试想一下,如果用继承的方式来实现不同配料组合下的各种Pizza。可以想见,整个子类将会非常庞大臃肿。那么这个时候,我们就可以通过装饰器模式来优雅的解决这个问题。在装饰器模式下,其通常有以下几个角色

  • 抽象组件接口 :被装饰对象 与 装饰者对象 需要实现同一个接口,以便装饰者对象拓展、增强被装饰对象的功能、行为。本文的例子下,就是一个Pizza接口,其定义了cook方法,用于烹饪出一个Pizza
  • 被装饰的具体对象:一个实现上述接口的具体类,其通常是按基础、通用的原则来实现接口中定义的方法。在这里即为KFCPizza类,其cook方法只烹饪了一个乞丐版的Pizza
  • 抽象装饰者:在抽象装饰者中,其内部会持有被装饰对象引用来转发请求,在Java中一般通过抽象类来定义该角色。在本文实例中即为PizzaDecorator类
  • 具体装饰者:其是抽象装饰者的实现类,用于实现拓展的功能。在本文例子中,即为BeefPizzaDecorator、DurianPizzaDecorator类。用于给乞丐版的Pizza添加各种配料

实现

现在我们利用Java来实现本文所说的例子,首先我们定义一个被装饰者、装饰者共有的接口

1
2
3
4
5
6
7
8
9
/**
* 抽象组件接口:Pizza
*/
public interface Pizza {
/**
* 烹饪Pizza
*/
void cook();
}

然后来实现一个被装饰的具体对象KFCPizza类,可以看到其只是烹饪一个啥配料都不加的Pizza

1
2
3
4
5
6
7
8
9
/**
* 具体组件, 被装饰对象: KFC Pizza
*/
public class KFCPizza implements Pizza {
@Override
public void cook() {
System.out.println("肯德基 Pizza");
}
}

现在我们需要来给Pizza加各种配料了,首先定义一个抽象装饰者。可以看到其有一个pizza字段用于持有被装饰包装的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 抽象装饰者
*/
public abstract class PizzaDecorator implements Pizza {
protected Pizza pizza;

public PizzaDecorator(Pizza pizza) {
this.pizza = pizza;
}

@Override
public void cook() {
if( pizza != null ) {
pizza.cook();
}
}
}

然后通过具体的装饰者来实现给Pizza添加不同的配料,这里我们提供了两个装饰者,分别用于给Pizza添加牛肉、榴莲

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
/**
* 具体装饰者:给Pizza加牛肉
*/
public class BeefPizzaDecorator extends PizzaDecorator {

public BeefPizzaDecorator(Pizza pizza) {
super(pizza);
}

@Override
public void cook() {
System.out.print("加牛肉,");
super.cook();
}
}
...
/**
* 具体装饰者:给Pizza加榴莲
*/
public class DurianPizzaDecorator extends PizzaDecorator {

public DurianPizzaDecorator(Pizza pizza) {
super(pizza);
}

@Override
public void cook() {
System.out.print("加榴莲,");
super.cook();
}
}

现在,我们来实际测试下,看看通过装饰器模式能不能吃到牛肉披萨。可以看到由于被装饰对象、装饰者都实现了同一个接口,所以可以包装多层整一个至尊披萨superPizza

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* Decorator Pattern 装饰者模式Demo
*/
public class DecoratorPatternDemo {
public static void main(String[] args) {
Pizza pizza = new KFCPizza();
pizza.cook();

// 给乞丐版Pizza加个牛肉
Pizza beefPizza = new BeefPizzaDecorator(pizza);
beefPizza.cook();

// 给牛肉Pizza再加个榴莲
Pizza superPizza = new DurianPizzaDecorator( beefPizza );
superPizza.cook();
}
}

测试结果符合预期

figure 1.png

参考文献

  1. Head First 设计模式 弗里曼著
0%