通常情况下,我们使用继承或组合来扩展一个类的行为,但是这些都是在编译时完成的,它适用于类的所有实例,并且随着扩展功能的增多,子类会很膨胀,我们不能在运行时添加或删除任何现有行为来达到复用的目的。为了解决这个问题,于是就有了装饰器模式。

比如我们熟悉的java IO类中就有大量装饰器模式的使用。例如FilterInputStream、FilterOutputStream、FilterReader、BufferedWriter等。

定义:

  • 定义

    装饰器(Decorator)模式一种结构型设计模式,是一种用于代替继承的技术,指在不改变现有对象结构的情况下,动态地给当前对象添加一些额外的功能。

    *装饰器模式的意图在于:运行时修改对象的功能,比生成子类更加灵活。*

img

结构

装饰器模式的结构:

1)抽象构件(Component)角色:定义一个抽象接口以规范子类的对象的行为,所有的包装类(装饰对象)和被包装类(真实对象)都继承自这个接口。比如:io流中InputStream、OutPutStream、Reader、Writer;

2)具体构件(ConcreteComponent)角色:指被包装类的实现类(真实对象)。比如:io流中FileInputStream、FileOutputStream、ObjectInputStream等;

3)装饰(Decorator)角色:所有包装类,都继承自Decorator(抽象)类,可以通过其子类扩展具体构件的功能。比如:io流中的FilterInputStream、FilterOutputStream;

4)具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,用于扩充被包装类的功能。比如:io流中的BufferedInputStream、DataInputStream、BufferedOutputStream;

对于java io流中的输入流而言,它的体系结构如上图所示。

Component角色:InputStream声明了输入流的实现规范;

ConcreteComponent角色:FileInputStream,ObjectInputStream、ByteArrayInputStream、PipedInputStream是输入流中为不同场景提供的实现;

Decorator角色:FilterInputStream抽象装饰,是为了增强ConcreteComponent角色的抽象类;

ConcreteDecorator角色:BufferedInputStream、DataInputStream是装饰器的实现类,是具体的增强方式。

以前我们用io流去读一个文件的时候,是不是经常会加一个缓冲流,提高数据读取的效率。

1
2
3
4
5
6
InputStream in = new FileInputStream("D:\\bigTest.txt");
InputStream bin = new BufferedInputStream(in);
byte[] data = new byte[1024];
while (bin.read(data) != -1) {
//...
}

那为什么不直接给FileInputStream接入缓存功能或者增加一个具有缓存功能的FileInputStream的子类呢?

由上图我们可以看到,如果InputStream只有一个实现类的话,这样做完全没有问题,可是InputStream的实现类有很多个,如果给每个子类都加加上增强功能的子类,可想而知,此时的子类数量是非常庞大的。而我们的装饰器模式就是用来代替继承并且增强对象的。JDK中装饰器模式的应用:

java io流中的FilterInputStream、FilterOutputStream、FilterReader、BufferedWriter等都是抽象装饰类。

样例

快餐店有炒面、炒饭这些快餐,可以额外附加鸡蛋、火腿、培根这些配菜,当然加配菜需要额外加钱,每个配菜的价钱通常不太一样,那么计算总价就会显得比较麻烦。

image-20220818230934956

代码如下:

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
//快餐接口
public abstract class FastFood {
private float price;
private String desc;

public FastFood() {
}

public FastFood(float price, String desc) {
this.price = price;
this.desc = desc;
}

public void setPrice(float price) {
this.price = price;
}

public float getPrice() {
return price;
}

public String getDesc() {
return desc;
}

public void setDesc(String desc) {
this.desc = desc;
}

public abstract float cost(); //获取价格
}

//炒饭
public class FriedRice extends FastFood {

public FriedRice() {
super(10, "炒饭");
}

public float cost() {
return getPrice();
}
}

//炒面
public class FriedNoodles extends FastFood {

public FriedNoodles() {
super(12, "炒面");
}

public float cost() {
return getPrice();
}
}

//配料类
public abstract class Garnish extends FastFood {

private FastFood fastFood;

public FastFood getFastFood() {
return fastFood;
}

public void setFastFood(FastFood fastFood) {
this.fastFood = fastFood;
}

public Garnish(FastFood fastFood, float price, String desc) {
super(price,desc);
this.fastFood = fastFood;
}
}

//鸡蛋配料
public class Egg extends Garnish {

public Egg(FastFood fastFood) {
super(fastFood,1,"鸡蛋");
}

public float cost() {
return getPrice() + getFastFood().getPrice();
}

@Override
public String getDesc() {
return super.getDesc() + getFastFood().getDesc();
}
}

//培根配料
public class Bacon extends Garnish {

public Bacon(FastFood fastFood) {

super(fastFood,2,"培根");
}

@Override
public float cost() {
return getPrice() + getFastFood().getPrice();
}

@Override
public String getDesc() {
return super.getDesc() + getFastFood().getDesc();
}
}

//测试类
public class Client {
public static void main(String[] args) {
//点一份炒饭
FastFood food = new FriedRice();
//花费的价格
System.out.println(food.getDesc() + " " + food.cost() + "元");

System.out.println("========");
//点一份加鸡蛋的炒饭
FastFood food1 = new FriedRice();

food1 = new Egg(food1);
//花费的价格
System.out.println(food1.getDesc() + " " + food1.cost() + "元");

System.out.println("========");
//点一份加培根的炒面
FastFood food2 = new FriedNoodles();
food2 = new Bacon(food2);
//花费的价格
System.out.println(food2.getDesc() + " " + food2.cost() + "元");
}
}

好处:

  • 饰者模式可以带来比继承更加灵活性的扩展功能,使用更加方便,可以通过组合不同的装饰者对象来获取具有不同行为状态的多样化的结果。装饰者模式比继承更具良好的扩展性,完美的遵循开闭原则,继承是静态的附加责任,装饰者则是动态的附加责任。

  • 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

image-20220818231707402

总结及建议

​ 装饰器提供运行时修改能力,因此更加灵活。当可供选择的数量越多时,使用装饰器模式会更加灵活。

应用场景:

1)当您需要能够在运行时为对象分配额外的行为而不破坏使用这些对象的代码时,请使用装饰器模式。

2)当需要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时(生成子类会产生大量子类),请使用装饰器模式。

3)对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,而采用装饰器模式却很好实现时。

代理和装饰者的区别

静态代理和装饰者模式的区别:

  • 相同点:
    • 都要实现与目标类相同的业务接口
    • 在两个类中都要声明目标对象
    • 都可以在不修改目标类的前提下增强目标方法
  • 不同点:
    • 目的不同
      装饰者是为了增强目标对象
      静态代理是为了保护和隐藏目标对象
    • 获取目标对象构建的地方不同
      装饰者是由外界传递进来,可以通过构造方法传递
      静态代理是在代理类内部创建,以此来隐藏目标对象