Java 常用设计模式(二):装饰器、适配器、责任链、模板方法、策略与观察者

前言

上一篇整理设计模式时,我越来越觉得,设计模式最难的部分不是代码,而是判断"这里到底需不需要用"。很多模式的结构看起来都像增加了一层接口和实现类,如果只背定义,到了项目里还是容易混淆。

这次选择的六种模式,刚好对应六类常见变化。

一、装饰器模式:不改原对象,动态叠加功能

假设系统中有一杯基础咖啡,后来需要支持加牛奶、加糖。如果为每一种组合都创建子类,很快就会出现"牛奶咖啡""加糖咖啡""牛奶加糖咖啡"等一堆类。

装饰器模式的思路,是让装饰对象和原对象实现同一个接口,并在内部持有被装饰对象:

复制代码
public interface Coffee {
    String description();
    int price();
}

public class MilkDecorator implements Coffee {
    private final Coffee coffee;

    public MilkDecorator(Coffee coffee) {
        this.coffee = coffee;
    }

    @Override
    public String description() {
        return coffee.description() + " + 牛奶";
    }

    @Override
    public int price() {
        return coffee.price() + 3;
    }
}

使用时可以按需要组合:

复制代码
Coffee coffee = new MilkDecorator(new SimpleCoffee());

装饰器比继承灵活的地方在于,功能可以在运行时组合,不需要提前为所有组合创建子类。

二、适配器模式:让不兼容的接口能够合作

项目需要接入第三方短信平台时,经常会遇到这种情况:系统希望统一调用 send(phone, content),供应商 SDK 却提供了完全不同的方法和参数对象。

如果业务代码到处直接调用供应商 SDK,后面切换平台会很痛苦。可以先定义项目自己的接口:

复制代码
public interface SmsService {
    void send(String phone, String content);
}

再编写适配器完成转换:

复制代码
public class VendorSmsAdapter implements SmsService {
    private final VendorClient client;

    public VendorSmsAdapter(VendorClient client) {
        this.client = client;
    }

    @Override
    public void send(String phone, String content) {
        VendorRequest request = new VendorRequest(phone, content);
        client.pushMessage(request);
    }
}

业务层只依赖 SmsService,并不知道第三方方法叫 pushMessage。以后替换供应商,只需增加新的适配器。

三、责任链模式:让请求依次经过多个处理器

提交订单前,可能需要进行参数校验、库存校验和风控校验。最直接的写法是把所有逻辑放进一个大方法,时间久了,这个方法会越来越长。

责任链模式把每一步拆成独立处理器,并让请求沿着链条向后传递:

复制代码
public abstract class OrderHandler {
    private OrderHandler next;

    public OrderHandler next(OrderHandler next) {
        this.next = next;
        return next;
    }

    public void handle(Order order) {
        check(order);
        if (next != null) {
            next.handle(order);
        }
    }

    protected abstract void check(Order order);
}

组装链条:

复制代码
OrderHandler param = new ParamHandler();
OrderHandler stock = new StockHandler();
OrderHandler risk = new RiskHandler();

param.next(stock).next(risk);
param.handle(order);

每个处理器只做一件事。校验失败时可以抛出异常或停止传递,新增校验也不需要修改其他处理器。

四、模板方法模式:固定整体流程,开放部分步骤

假设系统支持 Excel 和 CSV 文件导入。二者都需要经历读取文件、校验数据、保存数据三个阶段,区别主要在读取方式。

模板方法模式把稳定流程放在父类中,把有差异的步骤留给子类:

复制代码
public abstract class DataImporter {

    public final void importData(String file) {
        List<Row> rows = read(file);
        validate(rows);
        save(rows);
    }

    protected abstract List<Row> read(String file);

    protected void validate(List<Row> rows) {
        // 通用校验
    }

    protected void save(List<Row> rows) {
        // 通用保存
    }
}

ExcelImporterCsvImporter 只需要实现自己的 read()importData() 通常声明为 final,避免子类随意打乱流程顺序。

五、策略模式:把可替换算法封装起来

电商系统可能根据普通会员、VIP 和企业客户使用不同优惠算法。把所有规则写在一个 if-else 中,新增类型时就要修改原方法。

策略模式为不同算法提供统一接口:

复制代码
public interface DiscountStrategy {
    BigDecimal calculate(BigDecimal amount);
}

@Component("vip")
public class VipDiscountStrategy implements DiscountStrategy {
    @Override
    public BigDecimal calculate(BigDecimal amount) {
        return amount.multiply(new BigDecimal("0.90"));
    }
}

在 Spring 中,可以直接注入所有策略:

复制代码
@Service
public class DiscountService {
    private final Map<String, DiscountStrategy> strategies;

    public DiscountService(Map<String, DiscountStrategy> strategies) {
        this.strategies = strategies;
    }

    public BigDecimal calculate(String type, BigDecimal amount) {
        DiscountStrategy strategy = strategies.get(type);
        if (strategy == null) {
            throw new IllegalArgumentException("未知会员类型");
        }
        return strategy.calculate(amount);
    }
}

新增优惠规则时,只需增加策略实现,原有计算流程基本不用改。

六、观察者模式:状态变化后通知多个对象

订单支付成功后,系统可能需要发送通知、增加积分、记录审计日志。如果支付服务直接依赖这些模块,每增加一个后续动作都要修改支付代码。

观察者模式让发布者只负责发布事件,由不同监听者分别处理:

复制代码
public record OrderPaidEvent(Long orderId) {
}

@Service
public class PayService {
    private final ApplicationEventPublisher publisher;

    public void pay(Long orderId) {
        // 完成支付
        publisher.publishEvent(new OrderPaidEvent(orderId));
    }
}

监听者只关注自己需要处理的动作:

复制代码
@Component
public class PointListener {

    @EventListener
    public void addPoint(OrderPaidEvent event) {
        // 增加积分
    }
}

发布者不需要知道有多少监听者,新增通知逻辑时也不必改支付服务。这能降低模块之间的直接依赖。

相关推荐
jvmind_dev29 分钟前
频繁 new JedisCluster 之后,对象池为什么回不去了?
java·后端
小雨笙笙36 分钟前
C语言:指针
c语言·开发语言
AKA__Zas40 分钟前
文件 I/O(速通版
java·intellij-idea·学习方法
学长毕业设计41 分钟前
基于SpringBoot的咖啡馆管理系统的设计与实现(源码+文档+讲解视频)
java·spring boot·后端
骇客野人43 分钟前
SpringBoot业财一体化系统设计和落地实施方案
java·spring boot·后端
学长毕业设计1 小时前
基于SpringBoot的教学资源推荐系统的设计与实现(源码+文档+讲解视频)
java·spring boot·后端
名字还没想好☜1 小时前
Java Cleaner 实战:替代废弃的 finalize() 做堆外资源清理,以及强引用导致永不回收的坑
java·后端·spring
LaughingZhu1 小时前
Product Hunt 每日热榜 | 2026-09-02
java·ide·intellij-idea
旧梦95271 小时前
Java EnumMap 详解:原理、用法与实战
java·开发语言