装饰者模式
欢迎来到我的 ChatGPT 中转站,极具性价比,为付费不方便的朋友提供便利,有需求的可以添加左侧 QQ 二维码,另外,邀请新用户能获取余额哦!最后说一句,那啥:请自觉遵守《生成式人工智能服务管理暂行办法》。
# 简介
装饰者模式,动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp)。
例子:星巴克咖啡订单项目(咖啡馆)
- 咖啡种类 / 单品咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡)
- 调料:Milk、Soy(豆浆)、Chocolate。
- 要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便。
- 使用 OO 的来计算不同种类咖啡的费用:客户可以点单品咖啡,也可以单品咖啡 + 调料组合。
# 传统方式
# 方案一
Drink 是一个抽象类,表示饮料。
des 就是对咖啡的描述,比如咖啡的名字。
cost () 方法就是计算费用,Drink 类中做成一个抽象方法。
Decaf 就是单品咖啡,继承 Drink,并实现 cost。
Espress && Milk 就是单品咖啡 + 调料,这个组合很多。
问题:这样设计,会有很多类,当我们增加一个单品咖啡,或者一个新的调料,类的数量就会倍增,就会出现类爆炸。
# 方案二
前面分析到方案 1 因为咖啡单品 + 调料组合会造成类的倍增,因此可以做改进,将调料内置到 Drink 类,这样就不会造成类数量过多。从而提高项目的维护性(如图)。milk、soy、chocolate 可以设计为 Boolean,表示是否要添加相应的调料。
这种方式可以控制类的数量,不至于造成很多的类。
但是在增加或者删除调料种类时,代码的维护量很大。
# 装饰者模式
# 代码示例
/**
* 饮料基类
*/
@Data
public abstract class Drink {
private String des; // 描述
private float price = 0.0f;
// 计算费用的抽象方法
// 子类来实现
public abstract float cost();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 咖啡
*/
public class Coffee extends Drink {
@Override
public float cost() {
return super.getPrice();
}
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
/**
* 无因咖啡
*/
public class DeCaf extends Coffee {
public DeCaf() {
setDes(" 无因咖啡 ");
setPrice(1.0f);
}
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
/**
* 意大利咖啡
*/
public class Espresso extends Coffee {
public Espresso() {
setDes(" 意大利咖啡 ");
setPrice(6.0f);
}
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
/**
* 长黑咖啡
*/
public class LongBlack extends Coffee {
public LongBlack() {
setDes(" 长黑咖啡 ");
setPrice(5.0f);
}
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
@AllArgsConstructor // 构造器
public class Decorator extends Drink {
private Drink drink; // 被装饰者
/**
* 咖啡的价格+自己的价格
*/
@Override
public float cost() {
return drink.cost() + getPrice();
}
/**
* 自己的描述+被装饰者的描述
*/
@Override
public String getDes() {
return drink.getDes() + super.getDes();
}
/**
* 自己的描述
* @return
*/
public String decoratorDesc() {
return super.getDes();
}
}
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
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
/**
* 牛奶调味料,继承装饰者类
*/
public class Milk extends Decorator {
public Milk(Drink drink) {
super(drink);
setDes(" 牛奶 ");
setPrice(2.0f);
}
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
/**
* 豆浆
*/
public class Soy extends Decorator {
public Soy(Drink drink) {
super(drink);
setDes(" 豆浆 ");
setPrice(1.0f);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
/**
* 巧克力
*/
public class Chocolate extends Decorator {
public Chocolate(Drink drink) {
super(drink);
setDes(" 巧克力 ");
setPrice(3.0f); // 调味品 的价格
}
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
/**
* 客户端
* 两种写法的结果是一样的,只不过第二种写法更简洁一些,因为它不需要创建额外的装饰者对象。然而,第一种写法可能在某些情况下更有用,例如,如果你需要保留对之前装饰步骤的引用,或者需要在装饰过程中进行额外的操作。
*/
public class CoffeeBar {
public static void main(String[] args) {
System.out.println("=========================写法一=============================");
// 1. 点一份 LongBlack
Drink drink1 = new LongBlack();
System.out.println(drink1.getDes() + ":费用=" + drink1.cost());
// 用装饰者类装饰一下 Drink
Decorator decorator = new Decorator(drink1);
// 2. 加入一份牛奶
decorator = new Milk(drink1);
System.out.println(
"加入一份" + decorator.decoratorDesc() + ",费用=" + decorator.getPrice() + ",总费用 = " + decorator.cost());
// 3. 加入一份豆浆
decorator = new Soy(decorator);
System.out.println(
"加入一份" + decorator.decoratorDesc() + ",费用=" + decorator.getPrice() + ",总费用 = " + decorator.cost());
// 4. 加一份巧克力
decorator = new Chocolate(decorator);
System.out.println(
"加入一份" + decorator.decoratorDesc() + ",费用=" + decorator.getPrice() + ",总费用 = " + decorator.cost());
System.out.println("小票 = " + decorator.getDes());
System.out.println("=========================写法二=============================");
// 1. 点一份 DeCafe
Drink drink2 = new DeCaf();
System.out.println("费用1=" + drink2.cost());
System.out.println("描述=" + drink2.getDes());
// 2. order 加入一份牛奶
drink2 = new Milk(drink2);
System.out.println("order 加入一份牛奶 费用 =" + drink2.cost());
System.out.println("order 加入一份牛奶 描述 = " + drink2.getDes());
// 3. order 加入一份巧克力
drink2 = new Chocolate(drink2);
System.out.println("order 加入一份牛奶 加入一份巧克力 费用 =" + drink2.cost());
System.out.println("order 加入一份牛奶 加入一份巧克力 描述 = " + drink2.getDes());
}
}
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
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
# 说明
Drink 类:
- 这是饮料基类,拥有描述(des)和价格(price)属性,以及一个抽象方法 cost () 用于计算费用。
Coffee 类:
- 继承自 Drink 类,表示普通的咖啡。它实现了 cost () 方法,直接返回价格。
DeCaf、Espresso、LongBlack 类:
- 这些是具体的咖啡类型,分别表示无因咖啡、意大利咖啡和长黑咖啡。它们继承自 Coffee 类,设置了描述和价格。
Decorator 类:
- 这是装饰者基类,继承自 Drink 类。它包含一个成员变量 drink,表示被装饰的饮料。
- 实现了 cost () 方法,计算费用时加上被装饰者的费用和自己的费用。
- 重写 getDes () 方法,返回被装饰者的描述加上自己的描述。
- 定义了 decoratorDesc () 方法,返回自己的描述。
Milk、Soy、Chocolate 类:
- 这些是具体的调味料装饰者,继承自 Decorator 类。它们分别表示牛奶、豆浆和巧克力,设置了描述和价格。
CoffeeBar 类:
- 这是客户端示例,演示了如何使用装饰者模式创建不同组合的咖啡。
- 使用了两种不同的写法演示,都创建了不同的咖啡组合,然后输出其描述和费用。
在 JDK 源码 InputStream 中体现了装饰者模式的应用。
上次更新: 2025/04/12, 07:54:33