📌 PDF :AI人工智能 --- 大模型微调与部署实战项目
大白话说Java设计模式-16-装饰器模式(业务实战篇):大白商城订单价格计算的"叠加魔法"
📌 一句话本质:装饰器模式就是"穿衣服",一层套一层,动态地给对象加新功能。
🏷️ 标签:装饰器模式 / Java 设计模式 / 价格计算 / Java IO / 大白商城 🎯 适合:初中级后端 / 想搞懂 Java IO 原理的工程师
目录
- 一、业务场景引入:订单价格怎么"动态叠加"?
- 二、反面教材:硬编码优惠逻辑的"灾难现场"
- [三、模式原理:装饰器的"层层包裹 + 一张图"](#三、模式原理:装饰器的"层层包裹 + 一张图")
- 四、实战代码:大白商城价格计算完整实现
- [五、装饰器 vs 适配器 / 代理 / 继承](#五、装饰器 vs 适配器 / 代理 / 继承)
- [六、Java IO 流是装饰器吗?](#六、Java IO 流是装饰器吗?)
- [七、工程决策 Checklist](#七、工程决策 Checklist)
- 八、与其他模式协作
- [九、本篇小结 + 下篇预告](#九、本篇小结 + 下篇预告)
一、业务场景引入:订单价格怎么"动态叠加"?
大白商城 2024 年双 11 大促,营销经理提了一个看似简单、实则"动全身"的需求:
"订单价格计算要支持动态叠加。可以同时叠加满减、折扣、运费、VIP 折扣、限时特价、限时秒杀......规则不固定,经常变。"
我打开老代码一看,简直崩溃:
java
/**
* 大白商城老的价格计算代码
*/
public class OrderPriceService {
public BigDecimal calculate(Order order) {
BigDecimal price = order.getGoodsAmount();
// 1️⃣ 满减
if (order.getGoodsAmount().compareTo(new BigDecimal("200")) >= 0) {
price = price.subtract(new BigDecimal("30"));
}
if (order.getGoodsAmount().compareTo(new BigDecimal("500")) >= 0) {
price = price.subtract(new BigDecimal("80"));
}
if (order.getGoodsAmount().compareTo(new BigDecimal("1000")) >= 0) {
price = price.subtract(new BigDecimal("200"));
}
// 2️⃣ 折扣
if (order.getUserLevel() == 1) {
price = price.multiply(new BigDecimal("0.95")); // VIP 95 折
} else if (order.getUserLevel() == 2) {
price = price.multiply(new BigDecimal("0.9")); // SVIP 9 折
}
// 3️⃣ 限时特价
if (order.getHasFlashSale()) {
price = price.multiply(new BigDecimal("0.8")); // 限时特价 8 折
}
// 4️⃣ 优惠券
if (order.getCouponId() != null) {
// ... 又是 50 行
}
// 5️⃣ 运费
if (price.compareTo(new BigDecimal("99")) < 0) {
price = price.add(new BigDecimal("10")); // 满 99 免运费
}
return price;
}
}
这种代码的痛点:
| 序号 | 问题 | 后果 |
|---|---|---|
| ① | 规则写死 | 加新规则要改 calculate |
| ② | 顺序硬编码 | "满减在前还是折扣在前"无法配置 |
| ③ | 业务代码膨胀 | 200 行 if-else 看不到核心逻辑 |
| ④ | 单元测试难 | 一个方法测所有规则 |
| ⑤ | 加规则改老代码 | 违反开闭原则 |
老板要的"加新规则不动老代码 + 规则顺序可配置",怎么搞?
答案就是------装饰器模式。
1.1 大白话讲透装饰器
继续打比方:
场景 :大白商城要算订单最终价格。每个价格规则就是一个"装饰器":
- 满减规则:穿一件"满减 30 元"的衣服 → 价格 -30
- 折扣规则:穿一件"VIP 95 折"的衣服 → 价格 ×0.95
- 运费规则:穿一件"加 10 元运费"的衣服 → 价格 +10
- 限时特价:穿一件"打 8 折"的衣服 → 价格 ×0.8
每个规则独立:
- 可以单独使用(只用满减)
- 可以叠加使用(满减 + 折扣 + 运费 + 特价)
- 顺序可调(先打折再满减 vs 先满减再打折,结果不同)
- 加新规则只加新衣服(不影响现有规则)
装饰器模式 = 一层套一层地给对象加新功能,每层独立,可任意叠加。
1.2 装饰器模式的 3 个真实场景
大白商城里,装饰器模式用在:
| 场景 | "被装饰者" | "装饰器" |
|---|---|---|
| 价格计算 | 基础价格 | 满减/折扣/运费/特价装饰器 |
| Java IO 流 | InputStream |
BufferedInputStream / DataInputStream / GZIPInputStream |
| Java 集合 | List |
synchronizedList / unmodifiableList |
| 商品 VO | 基础商品 | 加标签/加图片/加规格装饰器 |
| 报表导出 | Excel 模板 | 加边框/加图表/加水印装饰器 |
任何"动态叠加功能"的场景,都用装饰器。
二、反面教材:硬编码优惠逻辑的"灾难现场"
我们看 4 个反面教材,看它们是怎么一步步崩的。
2.1 反面教材 v1:硬编码 if-else
java
/**
* ❌ 反面教材 v1:硬编码 if-else
*/
public class PriceServiceV1 {
public BigDecimal calculate(Order order) {
BigDecimal price = order.getGoodsAmount();
if (order.getGoodsAmount().compareTo(new BigDecimal("200")) >= 0) {
price = price.subtract(new BigDecimal("30"));
}
if (order.getUserLevel() == 1) {
price = price.multiply(new BigDecimal("0.95"));
}
// ... 200 行 if-else
return price;
}
}
翻车现场:
| 序号 | 问题 | 后果 |
|---|---|---|
| ① | 加规则改老代码 | 改一个方法要回归所有规则 |
| ② | 规则顺序写死 | 业务要求改顺序要重新写 |
| ③ | 业务逻辑被淹没 | 200 行代码看不到核心 |
2.2 反面教材 v2:策略模式 + if-else
java
/**
* ❌ 反面教材 v2:策略模式但仍 if-else
*/
public class PriceServiceV2 {
public BigDecimal calculate(Order order) {
BigDecimal price = order.getGoodsAmount();
// 仍然有 if-else
if (order.getHasFullReduce()) {
price = new FullReduceStrategy().apply(price, order);
}
if (order.getUserLevel() == 1) {
price = new VipStrategy().apply(price, order);
}
// ...
}
}
翻车现场:
| 序号 | 问题 | 后果 |
|---|---|---|
| ① | 仍有 if-else | 改顺序要改 if-else |
| ② | 每加规则要改 PriceServiceV2 | 违反开闭原则 |
2.3 反面教材 v3:策略 Map
java
/**
* ❌ 反面教材 v3:策略 Map 但顺序硬编码
*/
public class PriceServiceV3 {
private static final List<PriceStrategy> STRATEGIES = List.of(
new FullReduceStrategy(),
new VipStrategy(),
new FreightStrategy()
);
public BigDecimal calculate(Order order) {
BigDecimal price = order.getGoodsAmount();
for (PriceStrategy strategy : STRATEGIES) {
price = strategy.apply(price, order);
}
return price;
}
}
翻车现场:
| 序号 | 问题 | 后果 |
|---|---|---|
| ① | 顺序写死 | List 顺序不能动态改 |
| ② | 加新规则要改 List | 不是完全开闭 |
2.4 反面教材 v4:继承扩展
java
/**
* ❌ 反面教材 v4:继承
*/
public abstract class PriceCalculator {
public abstract BigDecimal calculate(Order order);
}
public class FullReducePriceCalculator extends PriceCalculator {
@Override
public BigDecimal calculate(Order order) {
// 满减逻辑
}
}
public class FullReduceAndVipPriceCalculator extends PriceCalculator {
@Override
public BigDecimal calculate(Order order) {
// 满减 + VIP
}
}
// 4 个规则 = 2^4 = 16 个类
翻车现场:
| 序号 | 问题 | 后果 |
|---|---|---|
| ① | 类数量爆炸 | 4 个规则 = 16 个类 |
| ② | 无法动态组合 | 编译期就确定 |
2.5 4 个反面教材的共同病根
| 痛点 | 反模式方案能不能解决? |
|---|---|
| 加新规则不动老代码 | ❌ 全部要改 |
| 规则顺序可配置 | ❌ 全部写死 |
| 业务代码不被淹没 | ❌ 全部臃肿 |
| 任意组合规则 | ❌ 全部受限 |
必须上装饰器模式。
三、模式原理:装饰器的"层层包裹 + 一张图"
3.1 装饰器的 4 个核心角色
| 角色 | 职责 | 例子 |
|---|---|---|
| 抽象组件(Component) | 定义接口 | PriceCalculator |
| 具体组件(Concrete Component) | 基础实现 | BasePriceCalculator |
| 抽象装饰器(Decorator) | 持有组件引用 | PriceDecorator |
| 具体装饰器(Concrete Decorator) | 真正加功能 | FullReduceDecorator / VipDiscountDecorator / FreightDecorator |
关键点 :装饰器和被装饰者实现同一接口。
3.2 一张图看懂装饰器
调用方
↓
PriceCalculator (接口)
↓
BasePriceCalculator(基础价格 = 商品金额)
↓ 包装
FullReduceDecorator(满减 -30)
↓ 包装
VipDiscountDecorator(VIP 95 折)
↓ 包装
FreightDecorator(+10 运费)
↓ 包装
FlashSaleDecorator(限时特价 8 折)
↓
最终价格
3.3 装饰器的"灵魂三问"
Q1:装饰器 vs 继承,区别是什么?
答:
- 继承:编译期确定,类数量爆炸
- 装饰器 :运行期动态组合,类数量不增加
- 装饰器是"组合优于继承"的典型应用
Q2:装饰器 vs 适配器,区别是什么?
答:
- 装饰器 :不改变接口 ,包装自己
- 适配器 :改变接口 ,包装第三方
- 装饰器侧重"增强" ,适配器侧重"翻译"
Q3:装饰器 vs 代理,区别是什么?
答:
- 装饰器 :添加职责(增强功能)
- 代理 :控制访问(权限/远程/延迟)
- 装饰器侧重"功能" ,代理侧重"管控"
3.4 装饰器的核心特征
| 特征 | 解释 |
|---|---|
| 同接口 | 装饰器和被装饰者实现同一接口 |
| 持有引用 | 装饰器持有被装饰者引用 |
| 链式调用 | 装饰器调被装饰者的方法,再加自己的逻辑 |
| 可嵌套 | 装饰器可以包装另一个装饰器 |
四、实战代码:大白商城价格计算完整实现
下面是大白商城生产环境在用的装饰器实现,全套代码可直接复制到 IDEA 跑。
4.1 项目环境与依赖
pom.xml:
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/>
</parent>
<groupId>com.dabai.mall</groupId>
<artifactId>mall-design-pattern-16</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>mall-design-pattern-16</name>
<description>大白商城 - 设计模式 16 装饰器模式</description>
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
4.2 抽象组件:PriceCalculator
java
package com.dabai.mall.price;
import java.math.BigDecimal;
/**
* ✅ 抽象组件:价格计算器
* <p>
* 装饰器和被装饰者都实现这个接口
*
* @author 大白商城技术团队
*/
public interface PriceCalculator {
/**
* ✅ 计算价格
*/
BigDecimal calculate(Order order);
}
配套 Order:
java
package com.dabai.mall.price;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Order {
private Long orderId;
private String orderNo;
private BigDecimal goodsAmount; // 商品总额
private Integer userLevel; // 1:VIP 95折 2:SVIP 9折
private Boolean hasFullReduce; // 是否参与满减
private Boolean hasFlashSale; // 是否限时特价
private Long couponId; // 优惠券 ID
private BigDecimal couponDiscount; // 优惠券折扣
}
4.3 具体组件:BasePriceCalculator
java
package com.dabai.mall.price;
import java.math.BigDecimal;
/**
* ✅ 具体组件:基础价格计算器
* <p>
* 返回商品原金额
*/
public class BasePriceCalculator implements PriceCalculator {
@Override
public BigDecimal calculate(Order order) {
return order.getGoodsAmount();
}
}
4.4 抽象装饰器:PriceDecorator
java
package com.dabai.mall.price;
import java.math.BigDecimal;
/**
* ✅ 抽象装饰器:价格装饰器
* <p>
* 所有具体装饰器都继承这个类
*/
public abstract class PriceDecorator implements PriceCalculator {
/**
* ✅ 持有被装饰者引用
*/
protected final PriceCalculator wrapped;
public PriceDecorator(PriceCalculator wrapped) {
this.wrapped = wrapped;
}
/**
* ✅ 默认实现:调被装饰者的 calculate
* <p>
* 子类可以重写,加自己的逻辑
*/
@Override
public BigDecimal calculate(Order order) {
return wrapped.calculate(order);
}
}
4.5 具体装饰器:4 个优惠规则
FullReduceDecorator(满减):
java
package com.dabai.mall.price.decorator;
import com.dabai.mall.price.Order;
import com.dabai.mall.price.PriceCalculator;
import com.dabai.mall.price.PriceDecorator;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
/**
* ✅ 具体装饰器:满减
* <p>
* 满 200 减 30 / 满 500 减 80 / 满 1000 减 200
*/
@Slf4j
public class FullReduceDecorator extends PriceDecorator {
public FullReduceDecorator(PriceCalculator wrapped) {
super(wrapped);
}
@Override
public BigDecimal calculate(Order order) {
// 1️⃣ 先调被装饰者的 calculate
BigDecimal price = wrapped.calculate(order);
// 2️⃣ 加满减逻辑
if (Boolean.TRUE.equals(order.getHasFullReduce())) {
BigDecimal originalPrice = price;
if (price.compareTo(new BigDecimal("1000")) >= 0) {
price = price.subtract(new BigDecimal("200"));
} else if (price.compareTo(new BigDecimal("500")) >= 0) {
price = price.subtract(new BigDecimal("80"));
} else if (price.compareTo(new BigDecimal("200")) >= 0) {
price = price.subtract(new BigDecimal("30"));
}
log.info("【满减】从 {} 减到 {}", originalPrice, price);
}
return price;
}
}
VipDiscountDecorator(VIP 折扣):
java
package com.dabai.mall.price.decorator;
import com.dabai.mall.price.Order;
import com.dabai.mall.price.PriceCalculator;
import com.dabai.mall.price.PriceDecorator;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
/**
* ✅ 具体装饰器:VIP 折扣
* <p>
* VIP 95 折 / SVIP 9 折
*/
@Slf4j
public class VipDiscountDecorator extends PriceDecorator {
public VipDiscountDecorator(PriceCalculator wrapped) {
super(wrapped);
}
@Override
public BigDecimal calculate(Order order) {
// 1️⃣ 先调被装饰者的 calculate
BigDecimal price = wrapped.calculate(order);
// 2️⃣ 加 VIP 折扣
if (order.getUserLevel() != null) {
BigDecimal originalPrice = price;
if (order.getUserLevel() == 1) {
// VIP 95 折
price = price.multiply(new BigDecimal("0.95"));
} else if (order.getUserLevel() == 2) {
// SVIP 9 折
price = price.multiply(new BigDecimal("0.9"));
}
log.info("【VIP折扣】从 {} 到 {}", originalPrice, price);
}
return price;
}
}
FreightDecorator(运费):
java
package com.dabai.mall.price.decorator;
import com.dabai.mall.price.Order;
import com.dabai.mall.price.PriceCalculator;
import com.dabai.mall.price.PriceDecorator;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
/**
* ✅ 具体装饰器:运费
* <p>
* 满 99 免运费,否则加 10 元
*/
@Slf4j
public class FreightDecorator extends PriceDecorator {
private static final BigDecimal FREE_FREIGHT_THRESHOLD = new BigDecimal("99");
private static final BigDecimal FREIGHT_COST = new BigDecimal("10");
public FreightDecorator(PriceCalculator wrapped) {
super(wrapped);
}
@Override
public BigDecimal calculate(Order order) {
// 1️⃣ 先调被装饰者的 calculate
BigDecimal price = wrapped.calculate(order);
// 2️⃣ 加运费
if (price.compareTo(FREE_FREIGHT_THRESHOLD) < 0) {
price = price.add(FREIGHT_COST);
log.info("【运费】加 10 元, 最终 {}", price);
} else {
log.info("【运费】满 99 免运费");
}
return price;
}
}
FlashSaleDecorator(限时特价):
java
package com.dabai.mall.price.decorator;
import com.dabai.mall.price.Order;
import com.dabai.mall.price.PriceCalculator;
import com.dabai.mall.price.PriceDecorator;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
/**
* ✅ 具体装饰器:限时特价
* <p>
* 8 折优惠
*/
@Slf4j
public class FlashSaleDecorator extends PriceDecorator {
private static final BigDecimal FLASH_SALE_DISCOUNT = new BigDecimal("0.8");
public FlashSaleDecorator(PriceCalculator wrapped) {
super(wrapped);
}
@Override
public BigDecimal calculate(Order order) {
// 1️⃣ 先调被装饰者的 calculate
BigDecimal price = wrapped.calculate(order);
// 2️⃣ 加限时特价
if (Boolean.TRUE.equals(order.getHasFlashSale())) {
BigDecimal originalPrice = price;
price = price.multiply(FLASH_SALE_DISCOUNT);
log.info("【限时特价】从 {} 到 {}", originalPrice, price);
}
return price;
}
}
4.6 业务调用方:PriceService
java
package com.dabai.mall.price;
import com.dabai.mall.price.decorator.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
/**
* ✅ 价格服务:装饰器模式的调用方
* <p>
* 加新规则只加装饰器,PriceService 一行不改
*
* @author 大白商城技术团队
*/
@Slf4j
@Service
public class PriceService {
/**
* 默认价格计算链:基础 → 满减 → VIP → 限时特价 → 运费
*/
public BigDecimal calculatePrice(Order order) {
// ✅ 层层装饰:像穿衣服一样
PriceCalculator calculator = new BasePriceCalculator();
calculator = new FullReduceDecorator(calculator);
calculator = new VipDiscountDecorator(calculator);
calculator = new FlashSaleDecorator(calculator);
calculator = new FreightDecorator(calculator);
BigDecimal finalPrice = calculator.calculate(order);
log.info("【PriceService】最终价格: {}", finalPrice);
return finalPrice;
}
/**
* 自定义价格计算链(规则顺序可配置)
*/
public BigDecimal calculatePrice(Order order, PriceCalculator calculator) {
return calculator.calculate(order);
}
}
关键点:
| 关键点 | 解释 |
|---|---|
new BasePriceCalculator() |
最内层(基础价格) |
new FullReduceDecorator(...) |
套一层(满减) |
new VipDiscountDecorator(...) |
再套一层(VIP) |
| 加新规则只加装饰器 | PriceService 不动 |
4.7 完整调用流程
调用 PriceService.calculatePrice(order)
↓
创建装饰链
↓
BasePriceCalculator(最内层:返回商品金额 1000)
↓ 包装
FullReduceDecorator(满减:1000 - 200 = 800)
↓ 包装
VipDiscountDecorator(VIP:800 × 0.95 = 760)
↓ 包装
FlashSaleDecorator(特价:760 × 0.8 = 608)
↓ 包装
FreightDecorator(运费:608 < 99? 是,加 10 = 618)
↓
最终价格:618
4.8 单元测试
java
package com.dabai.mall.price;
import com.dabai.mall.price.decorator.*;
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import static org.junit.jupiter.api.Assertions.*;
/**
* 装饰器完整单元测试
*/
class PriceDecoratorTest {
@Test
void testBasePrice() {
Order order = createOrder(new BigDecimal("1000"), 0, false, false);
PriceCalculator calculator = new BasePriceCalculator();
assertEquals(new BigDecimal("1000"), calculator.calculate(order));
}
@Test
void testFullReduceOnly() {
Order order = createOrder(new BigDecimal("1000"), 0, true, false);
PriceCalculator calculator = new FullReduceDecorator(new BasePriceCalculator());
assertEquals(new BigDecimal("800"), calculator.calculate(order)); // 1000 - 200
}
@Test
void testVipDiscountOnly() {
Order order = createOrder(new BigDecimal("1000"), 1, false, false);
PriceCalculator calculator = new VipDiscountDecorator(new BasePriceCalculator());
assertEquals(new BigDecimal("950.00"), calculator.calculate(order)); // 1000 × 0.95
}
@Test
void testAllDecorators() {
Order order = createOrder(new BigDecimal("1000"), 1, true, true);
// ✅ 完整装饰链
PriceCalculator calculator = new BasePriceCalculator();
calculator = new FullReduceDecorator(calculator); // 1000 - 200 = 800
calculator = new VipDiscountDecorator(calculator); // 800 × 0.95 = 760
calculator = new FlashSaleDecorator(calculator); // 760 × 0.8 = 608
calculator = new FreightDecorator(calculator); // 608 < 99, +10 = 618
assertEquals(new BigDecimal("618.00"), calculator.calculate(order));
}
@Test
void testCustomDecoratorOrder() {
// 顺序:VIP → 满减 → 运费
// 1000 × 0.95 = 950
// 950 < 1000, 满 500 减 80 = 870
// 870 > 99, 免运费 = 870
Order order = createOrder(new BigDecimal("1000"), 1, true, false);
PriceCalculator calculator = new BasePriceCalculator();
calculator = new VipDiscountDecorator(calculator);
calculator = new FullReduceDecorator(calculator);
calculator = new FreightDecorator(calculator);
assertEquals(new BigDecimal("870.00"), calculator.calculate(order));
}
@Test
void testFreightFree() {
Order order = createOrder(new BigDecimal("100"), 0, false, false);
PriceCalculator calculator = new FreightDecorator(new BasePriceCalculator());
// 100 < 99? 否,免运费
assertEquals(new BigDecimal("100"), calculator.calculate(order));
}
@Test
void testFreightCost() {
Order order = createOrder(new BigDecimal("50"), 0, false, false);
PriceCalculator calculator = new FreightDecorator(new BasePriceCalculator());
// 50 < 99, 加 10 = 60
assertEquals(new BigDecimal("60"), calculator.calculate(order));
}
private Order createOrder(BigDecimal amount, int userLevel,
boolean hasFullReduce, boolean hasFlashSale) {
return Order.builder()
.orderId(1L)
.orderNo("ORDER_001")
.goodsAmount(amount)
.userLevel(userLevel)
.hasFullReduce(hasFullReduce)
.hasFlashSale(hasFlashSale)
.build();
}
}
五、装饰器 vs 适配器 / 代理 / 继承
5.1 一张表看清区别
| 模式 | 关注点 | 是否改变接口 | 包装谁 | 适用场景 |
|---|---|---|---|---|
| 装饰器 | 增强功能 | ❌ 不改变 | 自己 | 动态叠加功能 |
| 适配器 | 翻译接口 | ✅ 改变 | 第三方 | 对接不兼容接口 |
| 代理 | 控制访问 | ❌ 不改变 | 自己 | 权限/远程/延迟 |
| 继承 | 扩展功能 | ✅ 改变 | 父类 | 类层次结构 |
5.2 一个具体例子区分 4 个模式
大白商城订单:
| 模式 | 例子 |
|---|---|
| 装饰器 | 价格计算:满减 + 折扣 + 运费叠加 |
| 适配器 | 物流对接:把顺丰/中通/京东包装成统一接口 |
| 代理 | Spring AOP:给 Service 包装日志/权限/事务 |
| 继承 | VipOrder extends Order:VIP 订单是 Order 子类 |
5.3 装饰器 vs 继承
| 维度 | 装饰器 | 继承 |
|---|---|---|
| 类数量 | 1 装饰器 + 1 被装饰者 | 类数量爆炸 |
| 灵活性 | 运行期动态组合 | 编译期确定 |
| 耦合度 | 弱(通过接口) | 强(编译期绑定) |
| 加新规则 | 加新装饰器 | 加新子类 |
业务启示:
能用装饰器就别用继承。"组合优于继承"是 OO 设计的金科玉律。
5.4 决策树
要给对象加新功能?
├── 是
│ ├── 加的功能影响接口?
│ │ ├── 是 → ✅ 继承 / 桥接
│ │ └── 否
│ │ ├── 加的功能是"管控"(权限/远程)?
│ │ │ ├── 是 → ✅ 代理
│ │ │ └── 否
│ │ │ ├── 加的功能是"翻译"(兼容第三方)?
│ │ │ │ ├── 是 → ✅ 适配器
│ │ │ │ └── 否
│ │ │ │ └── 动态叠加功能?
│ │ │ │ ├── 是 → ✅ 装饰器
│ │ │ │ └── 否 → 普通方法
└── └── └── └── └── └──
六、Java IO 流是装饰器吗?
6.1 Java IO 的装饰器家族
Java IO 流是装饰器模式的"教科书级"实现:
java
/**
* ✅ Java IO 流的装饰器链
*/
// 1. 节点流(最内层)
InputStream fileIn = new FileInputStream("data.txt");
// 2. 装饰:缓冲(提高性能)
InputStream bufferedIn = new BufferedInputStream(fileIn);
// 3. 装饰:数据流(按类型读)
DataInputStream dataIn = new DataInputStream(bufferedIn);
// 4. 装饰:GZIP 解压
InputStream gzIn = new GZIPInputStream(dataIn);
// 5. 装饰:对象流
ObjectInputStream objIn = new ObjectInputStream(gzIn);
// 读取
Object obj = objIn.readObject();
装饰链:
FileInputStream(文件输入流)
↓ 包装
BufferedInputStream(缓冲流)
↓ 包装
DataInputStream(数据流)
↓ 包装
GZIPInputStream(解压流)
↓ 包装
ObjectInputStream(对象流)
6.2 Java IO 装饰器为什么设计得好?
关键点:
| 特性 | 解释 |
|---|---|
| 同接口 | 所有流都继承 InputStream |
| 持有引用 | 装饰器持有上游流 |
| 链式调用 | read() 一层层向上调 |
| 可嵌套 | 任意组合装饰器 |
6.3 大白商城的"抄作业"
java
/**
* 借鉴 Java IO:数据导出流
*/
public abstract class DataExporter {
public abstract byte[] export(Object data);
}
/**
* 节点导出器(最内层)
*/
public class JsonExporter extends DataExporter {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public byte[] export(Object data) {
try {
return objectMapper.writeValueAsBytes(data);
} catch (JsonProcessingException e) {
throw new RuntimeException("JSON 序列化失败", e);
}
}
}
/**
* 装饰器:压缩
*/
public class CompressExporter extends DataExporter {
private final DataExporter wrapped;
public CompressExporter(DataExporter wrapped) {
this.wrapped = wrapped;
}
@Override
public byte[] export(Object data) {
byte[] raw = wrapped.export(data);
// GZIP 压缩
// ...
return compressed;
}
}
/**
* 装饰器:加密
*/
public class EncryptExporter extends DataExporter {
private final DataExporter wrapped;
public EncryptExporter(DataExporter wrapped) {
this.wrapped = wrapped;
}
@Override
public byte[] export(Object data) {
byte[] raw = wrapped.export(data);
// AES 加密
// ...
return encrypted;
}
}
// 使用:JSON → 压缩 → 加密
DataExporter exporter = new JsonExporter();
exporter = new CompressExporter(exporter);
exporter = new EncryptExporter(exporter);
byte[] result = exporter.export(data);
七、工程决策 Checklist
7.1 ✅ 这 5 种情况,强烈建议用装饰器
| 序号 | 场景 | 原因 |
|---|---|---|
| ① | 动态叠加功能 | 价格计算 / IO 流 |
| ② | 避免继承爆炸 | 组合优于继承 |
| ③ | 运行时组合 | 不同客户不同配置 |
| ④ | 多层包装 | 加密 + 压缩 + 编码 |
| ⑤ | Java IO 风格 | 标准做法 |
7.2 ❌ 这 5 种情况,绝对不要用装饰器
| 序号 | 场景 | 原因 |
|---|---|---|
| ① | 只有 1 个功能 | 装饰器是多余的 |
| ② | 功能影响接口 | 用继承 |
| ③ | 业务逻辑复杂 | 装饰器只装饰不加工 |
| ④ | 顺序固定不能改 | 用普通方法 |
| ⑤ | 调试困难 | 嵌套深难定位 |
7.3 ⚠️ 装饰器的 6 大常见坑
| 序号 | 坑 | 表现 | 解决方案 |
|---|---|---|---|
| ① | 接口不一致 | 装饰器和被装饰者接口不同 | 严格遵守同接口 |
| ② | 装饰器过多 | 嵌套太深难调试 | 限制装饰器数量 |
| ③ | 状态污染 | 装饰器持有可变状态 | 装饰器无状态 |
| ④ | 顺序错乱 | 装饰顺序不对结果不对 | 明确文档化顺序 |
| ⑤ | 性能损耗 | 每层包装有调用开销 | 减少装饰层数 |
| ⑥ | 单元测试复杂 | 测一个要搭整个链 | 拆解装饰器单独测 |
7.4 面试官视角:装饰器高频追问
Q1:装饰器 vs 继承,怎么选?
答:能用装饰器就别用继承。装饰器是"组合优于继承"的典型应用。继承类数量爆炸(4 个规则 = 16 个子类),装饰器只需要 4 个 Decorator 类。
Q2:装饰器 vs 代理,区别是什么?
答:装饰器增强功能 (加职责),代理控制访问 (权限/远程/延迟)。装饰器包装自己,代理也包装自己,但关注点不同。
Q3:Java IO 流是装饰器吗?
答:是的,Java IO 流是装饰器模式的教科书级实现 。所有流都继承
InputStream/OutputStream,通过BufferedInputStream/DataInputStream/GZIPInputStream等装饰器叠加功能。
八、与其他模式协作
8.1 装饰器 + 工厂方法 = 工厂创建装饰器链
java
/**
* 装饰器 + 工厂:自动创建价格计算链
*/
public class PriceCalculatorFactory {
public static PriceCalculator createDefaultChain() {
PriceCalculator calculator = new BasePriceCalculator();
calculator = new FullReduceDecorator(calculator);
calculator = new VipDiscountDecorator(calculator);
calculator = new FreightDecorator(calculator);
return calculator;
}
}
8.2 装饰器 + 策略 = 不同装饰器策略
java
/**
* 装饰器 + 策略:不同客户用不同装饰链
*/
public class PriceStrategy {
public PriceCalculator getCalculator(String customerType) {
PriceCalculator base = new BasePriceCalculator();
return switch (customerType) {
case "vip" -> new VipDiscountDecorator(base);
case "new_user" -> new NewUserDecorator(base);
case "flash_sale" -> new FlashSaleDecorator(base);
default -> base;
};
}
}
8.3 装饰器 + 责任链 = 多个装饰器串联
场景 :装饰器本身就是链式调用,和责任链是天然组合。
java
/**
* 装饰器链 = 责任链
*/
PriceCalculator chain = new BasePriceCalculator();
chain = new FullReduceDecorator(chain); // 节点 1
chain = new VipDiscountDecorator(chain); // 节点 2
chain = new FlashSaleDecorator(chain); // 节点 3
chain = new FreightDecorator(chain); // 节点 4
// 调用 calculate() 像责任链一样传递
8.4 大白商城模式协作全景图
┌──────────────┐
│ 装饰器 │ ← 本篇
└──────┬───────┘
│
┌───────────┬───────┼───────┬───────────┐
│ │ │ │ │
┌───▼───┐ ┌────▼───┐ ┌▼────┐ ┌▼─────┐ ┌───▼────┐
│工厂方法│ │ 策略 │ │责任链│ │适配器│ │ 代理 │
│(创建) │ │(切换) │ │(链) │ │(翻译)│ │(管控) │
└───────┘ └────────┘ └──────┘ └──────┘ └───────┘
04 篇 25 篇 31 篇 14 篇 12 篇
九、本篇小结 + 下篇预告
9.1 本篇小结(5 个核心要点)
- 本质 :装饰器 = 层层包裹,动态叠加功能 ,不改变接口。
- 场景 :价格计算 / IO 流 / 报表导出,动态叠加用装饰器。
- 对比:装饰器 vs 适配器(增强 vs 翻译)/ 装饰器 vs 代理(增强 vs 管控)/ 装饰器 vs 继承(组合 vs 类爆炸)。
- Java IO :装饰器模式的教科书级实现,5+ 种流任意叠加。
- 避坑:接口不一致 / 装饰器过多 / 顺序错乱 / 状态污染。
9.2 一句话总结
装饰器不是"换种方式调方法",是"像穿衣服一样一层层加新功能" 。大白商城日均 50 万订单的价格计算,从 200 行 if-else 变成 4 个 Decorator 自由叠加------加新规则只加 Decorator,业务代码完全不动。
9.3 知识脑图
装饰器模式
├── 4 大角色
│ ├── 抽象组件(PriceCalculator)
│ ├── 具体组件(BasePriceCalculator)
│ ├── 抽象装饰器(PriceDecorator)
│ └── 具体装饰器(FullReduce / Vip / FlashSale / Freight)
├── 4 个核心特性
│ ├── 同接口
│ ├── 持有引用
│ ├── 链式调用
│ └── 可嵌套
├── 模式对比
│ ├── vs 适配器(增强 vs 翻译)
│ ├── vs 代理(增强 vs 管控)
│ └── vs 继承(组合 vs 类爆炸)
├── Java IO
│ ├── FileInputStream(节点)
│ ├── BufferedInputStream(缓冲)
│ ├── DataInputStream(类型)
│ └── GZIPInputStream(解压)
└── 模式协作
├── + 工厂方法(创建装饰链)
├── + 策略(不同客户不同链)
└── + 责任链(链式传递)
9.4 下篇预告
第 17 篇【装饰器模式 - 源码剖析篇】:JDK / Spring 中的装饰器实现
下一篇我们会深入源码,回答三个问题:
- JDK 的
InputStream/OutputStream装饰器家族怎么实现的? - JDK 的
Collections.synchronizedList/unmodifiableList怎么用装饰器包装集合? - Spring 的
BeanDefinitionDecorator怎么装饰 Bean 定义?
并附完整的源码解读 + 流程图 + 大白商城的"抄作业"实践。
觉得对您有帮助,麻烦 点点关注啦 ,您的关注是我创作的最大动力~ 🎯