mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
2724 words
7 minutes
Java设计模式
2024-12-14

Java 设计模式实用笔记#

设计模式不是为了把代码写得很“高级”,而是为了解决反复出现的代码组织问题。

比如后端开发里经常遇到这些场景:

  • 支付方式越来越多:微信、支付宝、银行卡。
  • 通知渠道越来越多:短信、邮件、站内信。
  • 订单状态越来越复杂:待支付、已支付、已取消、已退款。
  • 接口调用前后要加日志、鉴权、限流、缓存。
  • 多个业务步骤有固定流程,但每一步细节又不同。

如果所有逻辑都堆在 if else 里,代码很快就会变得难读、难测、难扩展。

设计模式的核心作用就是:让变化的地方更容易变化,让稳定的地方保持稳定。


一、设计模式先解决什么问题#

学习设计模式时,不要先背名字,而要先想它解决什么问题。

问题常见模式
对象怎么创建更灵活工厂、建造者、单例
多种算法或规则怎么切换策略
固定流程里某些步骤不同模板方法
请求要经过多个处理节点责任链
不改原类的情况下增强能力代理、装饰器
老接口和新接口不兼容适配器
一个事件发生后通知多个对象观察者

设计模式不是越多越好。项目刚开始很简单时,直接写清楚就行。等代码里出现明显重复、分支爆炸、扩展困难时,再考虑引入模式。


二、单例模式:全局只需要一个对象#

单例模式保证一个类在系统中只有一个实例。

常见场景:

  • 配置管理器。
  • 线程池。
  • 本地缓存对象。
  • 工具类实例。

简单写法:

public class AppConfig {
private static final AppConfig INSTANCE = new AppConfig();
private AppConfig() {
}
public static AppConfig getInstance() {
return INSTANCE;
}
}

使用:

AppConfig config = AppConfig.getInstance();

不过在 Spring 项目里,很多对象默认就是单例 Bean:

@Service
public class OrderService {
}

默认情况下,Spring 容器里只会有一个 OrderService 实例。所以真实项目里,不一定要手写单例,交给 Spring 管理通常更合适。

注意:单例不要滥用。全局可变状态太多,会让代码难测试、难排查问题。


三、工厂模式:把创建对象的逻辑收起来#

工厂模式用来封装对象创建过程。

假设有多个支付渠道:

public interface PayService {
void pay(String orderNo);
}

微信支付:

public class WeChatPayService implements PayService {
@Override
public void pay(String orderNo) {
System.out.println("微信支付:" + orderNo);
}
}

支付宝支付:

public class AliPayService implements PayService {
@Override
public void pay(String orderNo) {
System.out.println("支付宝支付:" + orderNo);
}
}

如果业务代码里到处这样写:

if ("wechat".equals(payType)) {
return new WeChatPayService();
} else if ("alipay".equals(payType)) {
return new AliPayService();
}

后续支付方式越多,代码越乱。

可以抽一个工厂:

public class PayServiceFactory {
public static PayService getPayService(String payType) {
if ("wechat".equals(payType)) {
return new WeChatPayService();
}
if ("alipay".equals(payType)) {
return new AliPayService();
}
throw new IllegalArgumentException("不支持的支付方式:" + payType);
}
}

调用方只关心拿到支付服务:

PayService payService = PayServiceFactory.getPayService("wechat");
payService.pay("ORDER_1001");

工厂模式的好处是:创建逻辑集中管理,调用方不用关心具体实现类怎么 new 出来。


四、策略模式:消灭一堆 if else#

策略模式适合处理“多种规则、多种算法、多种业务分支”的场景。

比如订单优惠:

  • 普通用户不打折。
  • VIP 用户打 9 折。
  • 新用户立减 20 元。

先定义策略接口:

public interface DiscountStrategy {
int calculate(int originAmount);
}

普通用户:

public class NormalDiscountStrategy implements DiscountStrategy {
@Override
public int calculate(int originAmount) {
return originAmount;
}
}

VIP 用户:

public class VipDiscountStrategy implements DiscountStrategy {
@Override
public int calculate(int originAmount) {
return (int) (originAmount * 0.9);
}
}

新用户:

public class NewUserDiscountStrategy implements DiscountStrategy {
@Override
public int calculate(int originAmount) {
return Math.max(originAmount - 20, 0);
}
}

上下文类:

public class DiscountContext {
private final DiscountStrategy strategy;
public DiscountContext(DiscountStrategy strategy) {
this.strategy = strategy;
}
public int calculate(int originAmount) {
return strategy.calculate(originAmount);
}
}

使用:

DiscountStrategy strategy = new VipDiscountStrategy();
DiscountContext context = new DiscountContext(strategy);
int finalAmount = context.calculate(100);

策略模式适合把一堆分支拆成多个小类。后续新增一种优惠规则,只要新增一个策略类,不需要改一大段 if else

在 Spring 中更常见的写法是把策略 Bean 放进 Map

@Service
public class DiscountService {
private final Map<String, DiscountStrategy> strategyMap;
public DiscountService(Map<String, DiscountStrategy> strategyMap) {
this.strategyMap = strategyMap;
}
public int calculate(String userType, int amount) {
DiscountStrategy strategy = strategyMap.get(userType);
if (strategy == null) {
throw new IllegalArgumentException("不支持的用户类型:" + userType);
}
return strategy.calculate(amount);
}
}

这种写法在支付、导出、通知、审核规则里都很常见。


五、模板方法模式:固定流程,不同步骤#

模板方法模式适合“流程固定,但部分步骤不同”的场景。

比如不同文件导入流程都类似:

读取文件 -> 校验数据 -> 保存数据 -> 返回结果

但 Excel、CSV、JSON 的读取方式不同。

抽象模板:

public abstract class ImportTemplate {
public final void importData(String filePath) {
String content = readFile(filePath);
validate(content);
save(content);
afterImport();
}
protected abstract String readFile(String filePath);
protected void validate(String content) {
System.out.println("校验数据");
}
protected void save(String content) {
System.out.println("保存数据");
}
protected void afterImport() {
System.out.println("导入完成");
}
}

Excel 导入:

public class ExcelImportTemplate extends ImportTemplate {
@Override
protected String readFile(String filePath) {
return "读取 Excel 文件:" + filePath;
}
}

CSV 导入:

public class CsvImportTemplate extends ImportTemplate {
@Override
protected String readFile(String filePath) {
return "读取 CSV 文件:" + filePath;
}
}

模板方法的关键是:父类控制流程,子类只改变化的步骤。

Spring 里很多类都有模板方法思想,比如 JdbcTemplate。它把连接获取、异常处理、资源释放这些固定流程封装起来,开发者只关心 SQL 和参数。


六、责任链模式:让请求一关一关处理#

责任链模式适合一个请求要经过多个处理节点的场景。

常见例子:

  • 登录请求:参数校验 -> 验证码校验 -> 密码校验 -> 权限校验。
  • 下单请求:库存校验 -> 优惠券校验 -> 风控校验 -> 创建订单。
  • 网关请求:黑名单校验 -> 限流 -> 鉴权 -> 转发。

定义处理器接口:

public interface OrderCheckHandler {
void check(OrderContext context);
}

库存校验:

public class StockCheckHandler implements OrderCheckHandler {
@Override
public void check(OrderContext context) {
System.out.println("校验库存");
}
}

优惠券校验:

public class CouponCheckHandler implements OrderCheckHandler {
@Override
public void check(OrderContext context) {
System.out.println("校验优惠券");
}
}

责任链执行器:

public class OrderCheckChain {
private final List<OrderCheckHandler> handlers;
public OrderCheckChain(List<OrderCheckHandler> handlers) {
this.handlers = handlers;
}
public void check(OrderContext context) {
for (OrderCheckHandler handler : handlers) {
handler.check(context);
}
}
}

上下文对象:

public class OrderContext {
private Long userId;
private Long productId;
private Integer count;
}

责任链的好处是:每个处理器只负责一件事,新增校验规则时,新增一个 Handler 即可。

Spring MVC 的拦截器、Filter 链,本质上也有责任链思想。


七、代理模式:不改原类,增强功能#

代理模式是在不修改原对象的情况下,给它增加额外能力。

常见场景:

  • 记录日志。
  • 统计耗时。
  • 权限校验。
  • 事务管理。
  • 缓存增强。

接口:

public interface UserService {
void createUser(String username);
}

真实对象:

public class UserServiceImpl implements UserService {
@Override
public void createUser(String username) {
System.out.println("创建用户:" + username);
}
}

代理对象:

public class UserServiceProxy implements UserService {
private final UserService target;
public UserServiceProxy(UserService target) {
this.target = target;
}
@Override
public void createUser(String username) {
long start = System.currentTimeMillis();
target.createUser(username);
long cost = System.currentTimeMillis() - start;
System.out.println("耗时:" + cost + "ms");
}
}

使用:

UserService userService = new UserServiceProxy(new UserServiceImpl());
userService.createUser("zhangsan");

Spring AOP 就大量使用了代理模式。比如 @Transactional 并不是把事务代码写进你的方法里,而是通过代理在方法执行前后开启、提交或回滚事务。


八、装饰器模式:一层一层增强对象#

装饰器模式和代理模式有点像,都是不直接修改原类来增强功能。

区别可以简单理解为:

  • 代理更强调控制访问,比如权限、事务、远程调用。
  • 装饰器更强调功能叠加,比如加糖、加冰、加珍珠。

举个通知发送例子:

public interface Notifier {
void send(String message);
}

基础短信通知:

public class SmsNotifier implements Notifier {
@Override
public void send(String message) {
System.out.println("短信通知:" + message);
}
}

增加日志装饰:

public class LogNotifierDecorator implements Notifier {
private final Notifier notifier;
public LogNotifierDecorator(Notifier notifier) {
this.notifier = notifier;
}
@Override
public void send(String message) {
System.out.println("发送前记录日志");
notifier.send(message);
System.out.println("发送后记录日志");
}
}

使用:

Notifier notifier = new LogNotifierDecorator(new SmsNotifier());
notifier.send("订单支付成功");

Java IO 流就是典型装饰器模式:

BufferedInputStream inputStream =
new BufferedInputStream(new FileInputStream("a.txt"));

FileInputStream 负责读取文件,BufferedInputStream 在外面加了一层缓冲能力。


九、适配器模式:让不兼容的接口一起工作#

适配器模式适合新老接口不一致、第三方接口和系统接口不一致的场景。

比如系统内部统一支付接口:

public interface PayClient {
void pay(String orderNo, int amount);
}

但某个第三方 SDK 的方法是这样的:

public class ThirdPartyPaySdk {
public void createPayment(String tradeNo, int money) {
System.out.println("第三方支付:" + tradeNo + ",金额:" + money);
}
}

可以写一个适配器:

public class ThirdPartyPayAdapter implements PayClient {
private final ThirdPartyPaySdk sdk;
public ThirdPartyPayAdapter(ThirdPartyPaySdk sdk) {
this.sdk = sdk;
}
@Override
public void pay(String orderNo, int amount) {
sdk.createPayment(orderNo, amount);
}
}

业务代码仍然只依赖 PayClient

PayClient payClient = new ThirdPartyPayAdapter(new ThirdPartyPaySdk());
payClient.pay("ORDER_1001", 100);

这样以后换支付 SDK,只需要改适配器,不需要大面积改业务代码。


十、观察者模式:一个事件通知多个对象#

观察者模式适合“一个事件发生后,要触发多个后续动作”的场景。

比如订单支付成功后:

  • 修改订单状态。
  • 增加用户积分。
  • 发送短信通知。
  • 推送站内信。
  • 记录支付日志。

如果全部写在支付方法里,方法会越来越长。

定义事件:

public class OrderPaidEvent {
private String orderNo;
public OrderPaidEvent(String orderNo) {
this.orderNo = orderNo;
}
public String getOrderNo() {
return orderNo;
}
}

监听器接口:

public interface OrderPaidListener {
void onPaid(OrderPaidEvent event);
}

短信监听器:

public class SmsOrderPaidListener implements OrderPaidListener {
@Override
public void onPaid(OrderPaidEvent event) {
System.out.println("发送支付成功短信:" + event.getOrderNo());
}
}

积分监听器:

public class PointOrderPaidListener implements OrderPaidListener {
@Override
public void onPaid(OrderPaidEvent event) {
System.out.println("增加用户积分:" + event.getOrderNo());
}
}

事件发布器:

public class OrderEventPublisher {
private final List<OrderPaidListener> listeners;
public OrderEventPublisher(List<OrderPaidListener> listeners) {
this.listeners = listeners;
}
public void publish(OrderPaidEvent event) {
for (OrderPaidListener listener : listeners) {
listener.onPaid(event);
}
}
}

Spring 里也有事件机制:

applicationEventPublisher.publishEvent(new OrderPaidEvent(orderNo));

观察者模式可以让主流程更干净,但也要注意:事件太多时,调用链会变得不直观,需要做好日志和监控。


十一、建造者模式:创建复杂对象更清晰#

如果一个对象字段很多,构造方法会变得很长:

Order order = new Order(1L, "ORDER_1001", 100, 1, "北京", "13800000000");

这种写法很难看出每个参数代表什么。

可以使用建造者模式:

public class Order {
private Long userId;
private String orderNo;
private int amount;
private int status;
private String address;
private String phone;
private Order() {
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private final Order order = new Order();
public Builder userId(Long userId) {
order.userId = userId;
return this;
}
public Builder orderNo(String orderNo) {
order.orderNo = orderNo;
return this;
}
public Builder amount(int amount) {
order.amount = amount;
return this;
}
public Builder status(int status) {
order.status = status;
return this;
}
public Builder address(String address) {
order.address = address;
return this;
}
public Builder phone(String phone) {
order.phone = phone;
return this;
}
public Order build() {
return order;
}
}
}

使用:

Order order = Order.builder()
.userId(1L)
.orderNo("ORDER_1001")
.amount(100)
.status(1)
.address("北京")
.phone("13800000000")
.build();

建造者模式适合字段多、可选参数多、对象创建过程比较复杂的场景。


十二、什么时候不要用设计模式#

设计模式不是越多越好。

下面这些情况可以先别急着套模式:

  • 代码只有两三个分支,而且短期不会扩展。
  • 项目还在原型阶段,需求变化非常快。
  • 团队成员都不熟悉某个模式,强行使用会增加理解成本。
  • 为了“看起来高级”而拆出大量接口和类。

不推荐:

一个简单的新增用户逻辑,拆成 UserFactory、UserBuilder、UserStrategy、UserProcessor、UserContext。

更推荐:

先写清楚,等重复和变化真的出现,再抽象。

设计模式的目的不是炫技,而是让代码更容易维护。


十三、我常用的判断方式#

写业务代码时,可以这样判断要不要用模式:

现象可以考虑
同一个方法里很多 if else,每个分支是一种规则策略模式
多个类流程一样,只有个别步骤不同模板方法
一个请求要经过多个校验或处理节点责任链
想在方法前后加日志、事务、权限代理模式
想给对象一层层叠加功能装饰器模式
第三方接口和系统接口不一致适配器模式
一个事件发生后要触发多个动作观察者模式
创建对象很复杂,构造参数很多建造者模式

学习设计模式最好的方式不是背定义,而是结合真实业务反复问自己:

这里变化的是什么?
这里稳定的是什么?
如果下个月新增一种规则,我要改多少代码?

如果一个设计能让新增需求时少改老代码、少影响主流程、少制造重复逻辑,那它就是有价值的设计。


总结#

设计模式可以简单分成三类:

  • 创建型:解决对象怎么创建,比如单例、工厂、建造者。
  • 结构型:解决类和对象怎么组合,比如代理、装饰器、适配器。
  • 行为型:解决对象之间怎么协作,比如策略、模板方法、责任链、观察者。

对后端开发来说,最常用的不是所有 23 种模式,而是少数几个高频模式:

  • 策略模式:处理多规则。
  • 工厂模式:封装对象创建。
  • 模板方法:固定流程。
  • 责任链:多步骤校验。
  • 代理模式:日志、事务、权限增强。
  • 适配器:对接第三方系统。
  • 观察者:事件驱动后续动作。

真正理解设计模式,不是能说出它的 UML 图,而是能在业务代码变复杂前,找到合适的方式把变化隔离出去。

Share

If this article helped you, please share it with others!

Java设计模式
https://mizuki.mysqil.com/posts/design-patterns/
Author
梦幻晨风
Published at
2024-12-14
License
CC BY-NC-SA 4.0

Some information may be outdated

Table of Contents