大白话说Java设计模式-16-装饰器模式(业务实战篇)

📌 PDF :AI人工智能 --- 大模型微调与部署实战项目

大白话说Java设计模式-16-装饰器模式(业务实战篇):大白商城订单价格计算的"叠加魔法"

📌 一句话本质:装饰器模式就是"穿衣服",一层套一层,动态地给对象加新功能。

🏷️ 标签:装饰器模式 / Java 设计模式 / 价格计算 / Java IO / 大白商城 🎯 适合:初中级后端 / 想搞懂 Java IO 原理的工程师


目录


一、业务场景引入:订单价格怎么"动态叠加"?

大白商城 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 个核心要点)

  1. 本质 :装饰器 = 层层包裹,动态叠加功能不改变接口
  2. 场景 :价格计算 / IO 流 / 报表导出,动态叠加用装饰器
  3. 对比:装饰器 vs 适配器(增强 vs 翻译)/ 装饰器 vs 代理(增强 vs 管控)/ 装饰器 vs 继承(组合 vs 类爆炸)。
  4. Java IO :装饰器模式的教科书级实现,5+ 种流任意叠加。
  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 中的装饰器实现

下一篇我们会深入源码,回答三个问题:

  1. JDK 的 InputStream / OutputStream 装饰器家族怎么实现的?
  2. JDK 的 Collections.synchronizedList / unmodifiableList 怎么用装饰器包装集合?
  3. Spring 的 BeanDefinitionDecorator 怎么装饰 Bean 定义?

并附完整的源码解读 + 流程图 + 大白商城的"抄作业"实践。


觉得对您有帮助,麻烦 点点关注啦 ,您的关注是我创作的最大动力~ 🎯

相关推荐
艺艺生辉1 小时前
从if-else到策略模式
后端·设计模式
小林ixn1 小时前
单例模式:从弹窗管理到全局状态,一个模式搞定
前端·javascript·设计模式
桦说编程1 小时前
盘点并发集合里那些容易误判的行为
java·后端·性能优化
IT爱学堂2 小时前
java版数据结构和算法+AI算法和技能学习指南
java·开发语言
evans在进步3 小时前
LeetCode 394:字符串解码——Java 单栈模拟与嵌套解析详解
java·python·leetcode
m0_547486664 小时前
《Java程序设计与实践 》全套PPT课件2026
java·开发语言
土司大王4 小时前
LeetCode hto100——字母异位词分组
java·算法·leetcode
码匠许师傅4 小时前
【C++ 面试真题】聊聊 C++ 中的 lambda 表达式
java·c++·面试
jason.zeng@15022075 小时前
linux的top命令
java·linux·服务器
workflower5 小时前
案例 :某交通厅基于AI技术的智能安全运营实践
网络·人工智能·安全·设计模式·机器人