笔记 笔记
首页
  • 开发工具
  • Java Web
  • Java 进阶
  • 容器化技术
  • Java 专栏

    • Java 核心技术面试精讲
    • Java 业务开发常见错误 100 例
  • 数据库专栏

    • MySQL 实战 45 讲
    • Redis 核心技术与实战
  • 安全专栏

    • OAuth 2.0 实战课
  • 计算机系统
  • 程序设计语言
  • 数据结构
  • 知识产权
  • 数据库
  • 面向对象
  • UML
  • 设计模式
  • 操作系统
  • 结构化开发
  • 软件工程
  • 计算机网络
  • 上午题错题
在线工具 (opens new window)

EasT-Duan

Java 开发
首页
  • 开发工具
  • Java Web
  • Java 进阶
  • 容器化技术
  • Java 专栏

    • Java 核心技术面试精讲
    • Java 业务开发常见错误 100 例
  • 数据库专栏

    • MySQL 实战 45 讲
    • Redis 核心技术与实战
  • 安全专栏

    • OAuth 2.0 实战课
  • 计算机系统
  • 程序设计语言
  • 数据结构
  • 知识产权
  • 数据库
  • 面向对象
  • UML
  • 设计模式
  • 操作系统
  • 结构化开发
  • 软件工程
  • 计算机网络
  • 上午题错题
在线工具 (opens new window)

购买兑换码请添加

添加时候请写好备注,否则无法通过。

  • 设计模式

    • 简介

    • 原则

    • UML

    • 创建型

    • 结构型

      • 适配器模式
      • 桥接模式
      • 装饰者模式
        • 简介
        • 传统方式
          • 方案一
          • 方案二
        • 装饰者模式
          • 代码示例
          • 说明
      • 组合模式
      • 外观模式
      • 享元模式
      • 代理模式
    • 行为型

  • JVM 详解

  • Linux

  • Redis

  • 分布式锁

  • Shiro

  • Gradle

  • Java 进阶
  • 设计模式
  • 结构型
EasT-Duan
2023-11-17
目录

装饰者模式

欢迎来到我的 ChatGPT 中转站,极具性价比,为付费不方便的朋友提供便利,有需求的可以添加左侧 QQ 二维码,另外,邀请新用户能获取余额哦!最后说一句,那啥:请自觉遵守《生成式人工智能服务管理暂行办法》。

# 简介

装饰者模式,动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp)。

例子:星巴克咖啡订单项目(咖啡馆)

  1. 咖啡种类 / 单品咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡)
  2. 调料:Milk、Soy(豆浆)、Chocolate。
  3. 要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便。
  4. 使用 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
/**
 * 咖啡
 */
public class Coffee extends Drink {

	@Override
	public float cost() {
		return super.getPrice();
	}
}
1
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
/**
 * 意大利咖啡
 */
public class Espresso extends Coffee {
	public Espresso() {
		setDes(" 意大利咖啡 ");
		setPrice(6.0f);
	}
}
1
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
@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
/**
 * 牛奶调味料,继承装饰者类
 */
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
/**
 * 豆浆
 */
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
/**
 * 巧克力
 */
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
/**
 * 客户端
 * 两种写法的结果是一样的,只不过第二种写法更简洁一些,因为它不需要创建额外的装饰者对象。然而,第一种写法可能在某些情况下更有用,例如,如果你需要保留对之前装饰步骤的引用,或者需要在装饰过程中进行额外的操作。
 */
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

# 说明

  1. Drink 类:

    • 这是饮料基类,拥有描述(des)和价格(price)属性,以及一个抽象方法 cost () 用于计算费用。
  2. Coffee 类:

    • 继承自 Drink 类,表示普通的咖啡。它实现了 cost () 方法,直接返回价格。
  3. DeCaf、Espresso、LongBlack 类:

    • 这些是具体的咖啡类型,分别表示无因咖啡、意大利咖啡和长黑咖啡。它们继承自 Coffee 类,设置了描述和价格。
  4. Decorator 类:

    • 这是装饰者基类,继承自 Drink 类。它包含一个成员变量 drink,表示被装饰的饮料。
    • 实现了 cost () 方法,计算费用时加上被装饰者的费用和自己的费用。
    • 重写 getDes () 方法,返回被装饰者的描述加上自己的描述。
    • 定义了 decoratorDesc () 方法,返回自己的描述。
  5. Milk、Soy、Chocolate 类:

    • 这些是具体的调味料装饰者,继承自 Decorator 类。它们分别表示牛奶、豆浆和巧克力,设置了描述和价格。
  6. CoffeeBar 类:

    • 这是客户端示例,演示了如何使用装饰者模式创建不同组合的咖啡。
    • 使用了两种不同的写法演示,都创建了不同的咖啡组合,然后输出其描述和费用。

在 JDK 源码 InputStream 中体现了装饰者模式的应用。

#设计模式
上次更新: 2025/04/12, 07:54:33
桥接模式
组合模式

← 桥接模式 组合模式→

最近更新
01
Reactor 核心
02-24
02
前置条件
10-30
03
计算机网络
09-13
更多文章>
Theme by Vdoing | Copyright © 2019-2025 powered by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式