目录
一、 创建型模式:解决对象创建的复杂问题
1. 单例模式:全局配置管理(无人售货柜项目)
场景痛点
解决方案:懒汉式单例(双重检查锁,线程安全)
应用效果
框架关联
2. 工厂方法模式:支付方式适配(电商项目)
场景痛点
解决方案:工厂方法模式(定义支付工厂,子类实现具体支付方式)
步骤 1:定义抽象产品(支付接口)
步骤 2:实现具体产品(各支付方式)
步骤 3:定义抽象工厂 + 具体工厂
步骤 4:业务层调用(无需关注具体实现)
应用效果
二、 结构型模式:优化代码结构,降低耦合
1. 代理模式:接口权限控制(无人售货柜后台)
场景痛点
解决方案:动态代理模式(JDK 动态代理,AOP 思想)
步骤 1:定义业务接口
步骤 2:实现目标对象(真实业务逻辑)
步骤 3:实现代理类(权限校验逻辑)
步骤 4:业务层使用代理
应用效果
框架关联
2. 装饰器模式:IO 流增强(无人售货柜日志模块)
场景痛点
解决方案:装饰器模式(动态给日志对象添加功能)
步骤 1:定义抽象组件(日志接口)
步骤 2:实现基础组件(本地文件日志)
步骤 3:实现装饰器(增强功能)
步骤 4:业务层组合装饰器
应用效果
框架关联
三、 行为型模式:规范对象交互,提升逻辑灵活性
1. 策略模式:订单价格计算(电商项目)
场景痛点
解决方案:策略模式(封装不同优惠策略,动态切换)
步骤 1:定义策略接口
步骤 2:实现具体策略
步骤 3:策略上下文(统一调用入口)
步骤 4:业务层调用
应用效果
2. 命令模式:设备指令下发(无人售货柜项目)
场景痛点
解决方案:命令模式(封装指令为对象,解耦指令发送者与执行者)
步骤 1:定义抽象命令
步骤 2:实现具体命令
步骤 3:实现命令调用者(指令管理器)
步骤 4:控制器调用
应用效果
四、 设计模式选型核心思路
五、 总结
设计模式的价值在于解决真实业务场景中的耦合、扩展、复用问题。本文结合电商订单、无人售货柜、支付系统等真实项目场景,拆解常用设计模式的落地思路与代码实现,聚焦 “为什么用”“怎么用”“解决什么问题” 三大核心。
一、 创建型模式:解决对象创建的复杂问题
创建型模式的核心目标是封装对象创建细节,让调用方无需关注初始化逻辑,提升创建灵活性。
1. 单例模式:全局配置管理(无人售货柜项目)
场景痛点
无人售货柜项目中,DeviceConfig包含设备的串口配置、网络参数、货道信息等全局配置,全系统需共享同一实例,且初始化时需从本地文件加载配置,避免重复 IO 操作。
解决方案:懒汉式单例(双重检查锁,线程安全)
java
运行
/** * 售货柜设备配置(单例模式) * 核心:全局唯一实例 + 延迟初始化 + 线程安全 */ public class DeviceConfig { // 1. 私有静态变量(volatile 禁止指令重排) private static volatile DeviceConfig instance; // 配置参数 private String serialPort; // 串口地址 private int baudRate; // 波特率 private Map<Integer, String> channelMap; // 货道映射 // 2. 私有构造器(禁止外部new) private DeviceConfig() { // 从本地配置文件加载参数(实际项目用Properties/YAML) loadConfigFromFile(); } // 3. 公共静态方法(双重检查锁,懒加载) public static DeviceConfig getInstance() { if (instance == null) { // 第一次检查:避免不必要的锁 synchronized (DeviceConfig.class) { // 加锁 if (instance == null) { // 第二次检查:防止多线程重复创建 instance = new DeviceConfig(); } } } return instance; } // 加载配置文件 private void loadConfigFromFile() { // 实际逻辑:读取config/device.properties this.serialPort = "/dev/ttyUSB0"; this.baudRate = 9600; this.channelMap = new HashMap<>(); channelMap.put(1, "可乐"); channelMap.put(2, "薯片"); } // getter方法 public String getSerialPort() { return serialPort; } public Map<Integer, String> getChannelMap() { return channelMap; } }应用效果
- 全系统共享同一配置实例,避免重复加载文件导致的性能损耗;
- 延迟初始化:首次调用
getInstance()才加载配置,减少项目启动时间; - 线程安全:双重检查锁保证多线程环境下不会创建多个实例。
框架关联
Spring 容器中的默认 Bean 是单例模式,底层通过DefaultSingletonBeanRegistry实现,原理与上述案例一致。
2. 工厂方法模式:支付方式适配(电商项目)
场景痛点
电商订单支付模块需支持微信支付、支付宝、银联三种方式,后续可能新增 “云闪付”。若直接在业务代码中用if-else判断支付类型,会导致代码臃肿、扩展困难(新增支付方式需修改原有代码,违反开闭原则)。
解决方案:工厂方法模式(定义支付工厂,子类实现具体支付方式)
步骤 1:定义抽象产品(支付接口)
java
运行
/** * 抽象支付产品 */ public interface Payment { // 发起支付 String pay(String orderId, BigDecimal amount); // 查询支付状态 boolean queryPayStatus(String orderId); }步骤 2:实现具体产品(各支付方式)
java
运行
// 微信支付实现 public class WechatPayment implements Payment { @Override public String pay(String orderId, BigDecimal amount) { // 调用微信支付SDK return "微信支付下单成功,订单号:" + orderId + ",金额:" + amount; } @Override public boolean queryPayStatus(String orderId) { return true; // 模拟查询结果 } } // 支付宝支付实现 public class AlipayPayment implements Payment { @Override public String pay(String orderId, BigDecimal amount) { return "支付宝支付下单成功,订单号:" + orderId + ",金额:" + amount; } @Override public boolean queryPayStatus(String orderId) { return true; } }步骤 3:定义抽象工厂 + 具体工厂
java
运行
/** * 抽象支付工厂 */ public interface PaymentFactory { Payment createPayment(); } // 微信支付工厂 public class WechatPaymentFactory implements PaymentFactory { @Override public Payment createPayment() { return new WechatPayment(); } } // 支付宝支付工厂 public class AlipayPaymentFactory implements PaymentFactory { @Override public Payment createPayment() { return new AlipayPayment(); } }步骤 4:业务层调用(无需关注具体实现)
java
运行
@Service public class OrderPayService { // 根据支付类型获取工厂,创建支付实例 public String doPay(String orderId, BigDecimal amount, String payType) { PaymentFactory factory; switch (payType) { case "WECHAT": factory = new WechatPaymentFactory(); break; case "ALIPAY": factory = new AlipayPaymentFactory(); break; default: throw new IllegalArgumentException("不支持的支付类型"); } Payment payment = factory.createPayment(); return payment.pay(orderId, amount); } }应用效果
- 开闭原则:新增 “云闪付” 只需添加
CloudPayment和CloudPaymentFactory,无需修改原有业务代码; - 职责单一:每种支付方式的逻辑封装在独立类中,便于维护和测试;
- 解耦:业务层只依赖
Payment接口,不依赖具体实现类。
二、 结构型模式:优化代码结构,降低耦合
结构型模式的核心目标是通过类 / 对象的组合,解决结构臃肿、扩展困难的问题。
1. 代理模式:接口权限控制(无人售货柜后台)
场景痛点
无人售货柜后台的DeviceManageService包含设备重启、货道修改、参数配置等敏感接口,需对调用方进行权限校验(如只有管理员角色才能调用)。若直接在接口中添加权限逻辑,会导致业务逻辑与权限逻辑耦合,且重复代码多。
解决方案:动态代理模式(JDK 动态代理,AOP 思想)
步骤 1:定义业务接口
java
运行
/** * 设备管理接口(被代理接口) */ public interface DeviceManageService { void restartDevice(String deviceId); // 重启设备 void updateChannel(String deviceId, Map<Integer, String> channelMap); // 修改货道 }步骤 2:实现目标对象(真实业务逻辑)
java
运行
/** * 真实设备管理服务 */ @Service public class DeviceManageServiceImpl implements DeviceManageService { @Override public void restartDevice(String deviceId) { System.out.println("重启设备:" + deviceId); // 实际逻辑:发送重启指令到硬件 } @Override public void updateChannel(String deviceId, Map<Integer, String> channelMap) { System.out.println("修改设备货道:" + deviceId + ",货道映射:" + channelMap); } }步骤 3:实现代理类(权限校验逻辑)
java
运行
/** * 权限代理类(JDK 动态代理) * 核心:在不修改目标对象的前提下,增强方法功能 */ public class PermissionProxy implements InvocationHandler { // 目标对象(被代理的真实对象) private final Object target; public PermissionProxy(Object target) { this.target = target; } /** * 代理方法:增强目标方法的执行逻辑 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 1. 前置增强:权限校验 checkPermission(); // 2. 执行目标方法 Object result = method.invoke(target, args); // 3. 后置增强:记录操作日志 logOperation(method.getName(), args); return result; } // 权限校验逻辑 private void checkPermission() { // 实际逻辑:从 ThreadLocal 获取当前用户角色 String role = UserContextHolder.getCurrentUser().getRole(); if (!"ADMIN".equals(role)) { throw new SecurityException("无权限操作设备!"); } } // 日志记录逻辑 private void logOperation(String methodName, Object[] args) { System.out.println("执行设备操作:" + methodName + ",参数:" + Arrays.toString(args)); } // 获取代理对象的工具方法 public static Object getProxy(Object target) { return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new PermissionProxy(target) ); } }步骤 4:业务层使用代理
java
运行
@Configuration public class DeviceConfig { @Bean public DeviceManageService deviceManageService() { // 创建真实对象 DeviceManageServiceImpl target = new DeviceManageServiceImpl(); // 生成代理对象并返回 return (DeviceManageService) PermissionProxy.getProxy(target); } } // 控制器调用 @RestController @RequestMapping("/device") public class DeviceController { @Autowired private DeviceManageService deviceManageService; @PostMapping("/restart/{deviceId}") public String restart(@PathVariable String deviceId) { deviceManageService.restartDevice(deviceId); return "操作成功"; } }应用效果
- 解耦:权限校验、日志记录与业务逻辑完全分离,符合单一职责原则;
- 可复用:代理逻辑可复用在其他需要权限控制的接口上;
- 无侵入:无需修改目标对象的代码,只需通过代理增强功能。
框架关联
Spring AOP 的底层就是动态代理模式,@Transactional、@Cacheable等注解的实现原理与上述案例一致。
2. 装饰器模式:IO 流增强(无人售货柜日志模块)
场景痛点
无人售货柜的日志模块需要记录设备操作日志,要求:
- 日志内容需格式化(包含时间、设备 ID、操作类型);
- 日志需同时写入本地文件 + 上传到云端;
- 后续可能新增 “加密日志” 功能。若直接写一个
LogService包含所有逻辑,会导致功能耦合、扩展困难。
解决方案:装饰器模式(动态给日志对象添加功能)
步骤 1:定义抽象组件(日志接口)
java
运行
/** * 抽象日志组件 */ public interface Logger { void log(String deviceId, String content); }步骤 2:实现基础组件(本地文件日志)
java
运行
/** * 基础日志组件:写入本地文件 */ public class FileLogger implements Logger { @Override public void log(String deviceId, String content) { // 实际逻辑:写入本地文件 /logs/device-{deviceId}.log System.out.println("【本地日志】设备:" + deviceId + ",内容:" + content); } }步骤 3:实现装饰器(增强功能)
java
运行
/** * 装饰器抽象类:遵循抽象组件接口,持有组件引用 */ public abstract class LoggerDecorator implements Logger { protected Logger logger; public LoggerDecorator(Logger logger) { this.logger = logger; } } // 装饰器1:格式化日志(添加时间戳) public class FormatLoggerDecorator extends LoggerDecorator { public FormatLoggerDecorator(Logger logger) { super(logger); } @Override public void log(String deviceId, String content) { // 格式化内容:[时间] 设备ID - 内容 String formatContent = String.format("[%s] %s - %s", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME), deviceId, content); // 调用被装饰者的方法 logger.log(deviceId, formatContent); } } // 装饰器2:上传云端日志 public class CloudLoggerDecorator extends LoggerDecorator { public CloudLoggerDecorator(Logger logger) { super(logger); } @Override public void log(String deviceId, String content) { // 1. 先执行被装饰者的逻辑(写入本地) logger.log(deviceId, content); // 2. 新增功能:上传到云端 uploadToCloud(deviceId, content); } private void uploadToCloud(String deviceId, String content) { System.out.println("【云端日志】设备:" + deviceId + ",内容:" + content); } }步骤 4:业务层组合装饰器
java
运行
@Service public class DeviceLogService { private final Logger logger; // 构造器注入:组合装饰器 public DeviceLogService() { // 基础组件:本地日志 Logger baseLogger = new FileLogger(); // 装饰器1:格式化 Logger formatLogger = new FormatLoggerDecorator(baseLogger); // 装饰器2:上传云端 this.logger = new CloudLoggerDecorator(formatLogger); } public void recordLog(String deviceId, String content) { logger.log(deviceId, content); } } // 调用示例 deviceLogService.recordLog("DEVICE001", "货道1补货完成"); // 输出结果: // 【本地日志】设备:DEVICE001,内容:[2025-12-26T15:30:00] DEVICE001 - 货道1补货完成 // 【云端日志】设备:DEVICE001,内容:[2025-12-26T15:30:00] DEVICE001 - 货道1补货完成应用效果
- 功能动态组合:按需组合装饰器(如只需要本地日志则不加
CloudLoggerDecorator); - 开闭原则:新增 “加密日志” 只需添加
EncryptLoggerDecorator,无需修改原有代码; - 职责单一:每个装饰器只负责一个增强功能,便于维护。
框架关联
JDK 的IO 流是装饰器模式的经典实现,如BufferedInputStream装饰FileInputStream,实现缓冲功能。
三、 行为型模式:规范对象交互,提升逻辑灵活性
行为型模式的核心目标是定义对象间的通信方式,解决行为协调、职责分配的问题。
1. 策略模式:订单价格计算(电商项目)
场景痛点
电商订单结算时,需根据用户等级、优惠券、促销活动计算最终价格:
- 普通用户:原价;
- VIP 用户:9 折;
- 新用户:满 100 减 20;
- 叠加优惠券:再减固定金额。若用
if-else实现,会导致逻辑混乱、扩展困难(新增优惠类型需修改结算方法)。
解决方案:策略模式(封装不同优惠策略,动态切换)
步骤 1:定义策略接口
java
运行
/** * 订单优惠策略接口 */ public interface DiscountStrategy { /** * 计算优惠后价格 * @param order 订单信息 * @return 优惠后金额 */ BigDecimal calculate(Order order); /** * 获取策略类型 */ String getType(); }步骤 2:实现具体策略
java
运行
// 策略1:普通用户(无优惠) public class NormalUserStrategy implements DiscountStrategy { @Override public BigDecimal calculate(Order order) { return order.getTotalAmount(); } @Override public String getType() { return "NORMAL"; } } // 策略2:VIP用户(9折) public class VipUserStrategy implements DiscountStrategy { @Override public BigDecimal calculate(Order order) { return order.getTotalAmount().multiply(new BigDecimal("0.9")); } @Override public String getType() { return "VIP"; } } // 策略3:新用户(满100减20) public class NewUserStrategy implements DiscountStrategy { @Override public BigDecimal calculate(Order order) { BigDecimal amount = order.getTotalAmount(); return amount.compareTo(new BigDecimal("100")) >= 0 ? amount.subtract(new BigDecimal("20")) : amount; } @Override public String getType() { return "NEW"; } }步骤 3:策略上下文(统一调用入口)
java
运行
/** * 策略上下文:封装策略选择逻辑,提供统一调用入口 */ @Service public class DiscountContext { // 策略容器:Spring 启动时自动注入所有策略实现类 private final Map<String, DiscountStrategy> strategyMap; // 构造器注入:Spring 会自动将所有 DiscountStrategy 实现类放入 Map public DiscountContext(List<DiscountStrategy> strategies) { this.strategyMap = new HashMap<>(); for (DiscountStrategy strategy : strategies) { strategyMap.put(strategy.getType(), strategy); } } /** * 计算订单最终价格 */ public BigDecimal calculateFinalPrice(Order order) { // 1. 获取用户类型对应的策略 String userType = order.getUser().getType(); DiscountStrategy strategy = strategyMap.getOrDefault(userType, new NormalUserStrategy()); // 2. 计算基础优惠价格 BigDecimal discountAmount = strategy.calculate(order); // 3. 叠加优惠券优惠 if (order.getCoupon() != null) { discountAmount = discountAmount.subtract(order.getCoupon().getAmount()); } // 4. 价格不能小于0 return discountAmount.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : discountAmount; } }步骤 4:业务层调用
java
运行
@Service public class OrderService { @Autowired private DiscountContext discountContext; public BigDecimal checkout(Order order) { return discountContext.calculateFinalPrice(order); } }应用效果
- 消除 if-else:策略选择逻辑由
strategyMap自动完成,代码更简洁; - 灵活扩展:新增 “黑钻用户 8 折” 策略,只需添加
BlackDiamondStrategy类,无需修改原有代码; - 便于测试:每个策略可单独测试,降低测试复杂度。
2. 命令模式:设备指令下发(无人售货柜项目)
场景痛点
无人售货柜后台需向设备下发多种指令:
- 货道库存查询指令;
- 设备重启指令;
- 商品价格更新指令;要求:
- 指令可异步执行(无需等待设备响应);
- 指令可记录日志(便于排查问题);
- 后续可新增指令类型(如 “清库存指令”)。
解决方案:命令模式(封装指令为对象,解耦指令发送者与执行者)
步骤 1:定义抽象命令
java
运行
/** * 设备指令抽象命令 */ public interface DeviceCommand { // 执行指令 void execute(); // 获取指令ID String getCommandId(); }步骤 2:实现具体命令
java
运行
/** * 具体命令1:库存查询指令 */ public class StockQueryCommand implements DeviceCommand { private final String deviceId; private final DeviceClient deviceClient; // 设备通信客户端(执行者) private final String commandId; public StockQueryCommand(String deviceId, DeviceClient deviceClient) { this.deviceId = deviceId; this.deviceClient = deviceClient; this.commandId = "CMD_" + System.currentTimeMillis(); } @Override public void execute() { // 调用执行者的方法:下发查询指令 String result = deviceClient.sendQueryStockCommand(deviceId); // 记录指令执行日志 System.out.println("执行库存查询指令:" + commandId + ",设备:" + deviceId + ",结果:" + result); } @Override public String getCommandId() { return commandId; } } /** * 具体命令2:价格更新指令 */ public class PriceUpdateCommand implements DeviceCommand { private final String deviceId; private final DeviceClient deviceClient; private final Map<Integer, BigDecimal> priceMap; // 货道-价格映射 private final String commandId; public PriceUpdateCommand(String deviceId, DeviceClient deviceClient, Map<Integer, BigDecimal> priceMap) { this.deviceId = deviceId; this.deviceClient = deviceClient; this.priceMap = priceMap; this.commandId = "CMD_" + System.currentTimeMillis(); } @Override public void execute() { String result = deviceClient.sendUpdatePriceCommand(deviceId, priceMap); System.out.println("执行价格更新指令:" + commandId + ",设备:" + deviceId + ",结果:" + result); } @Override public String getCommandId() { return commandId; } }步骤 3:实现命令调用者(指令管理器)
java
运行
/** * 命令调用者:设备指令管理器 * 核心:触发命令执行,无需关注命令具体逻辑 */ @Service public class DeviceCommandInvoker { @Autowired private DeviceClient deviceClient; // 线程池:异步执行指令 private final ExecutorService executor = Executors.newFixedThreadPool(5); /** * 下发库存查询指令 */ public String sendStockQueryCommand(String deviceId) { DeviceCommand command = new StockQueryCommand(deviceId, deviceClient); executor.submit(command::execute); // 异步执行 return command.getCommandId(); } /** * 下发价格更新指令 */ public String sendPriceUpdateCommand(String deviceId, Map<Integer, BigDecimal> priceMap) { DeviceCommand command = new PriceUpdateCommand(deviceId, deviceClient, priceMap); executor.submit(command::execute); return command.getCommandId(); } }步骤 4:控制器调用
java
运行
@RestController @RequestMapping("/device/command") public class DeviceCommandController { @Autowired private DeviceCommandInvoker commandInvoker; @PostMapping("/stock/{deviceId}") public String queryStock(@PathVariable String deviceId) { String commandId = commandInvoker.sendStockQueryCommand(deviceId); return "指令已下发,ID:" + commandId; } @PostMapping("/price/{deviceId}") public String updatePrice(@PathVariable String deviceId, @RequestBody Map<Integer, BigDecimal> priceMap) { String commandId = commandInvoker.sendPriceUpdateCommand(deviceId, priceMap); return "指令已下发,ID:" + commandId; } }应用效果
- 解耦:指令发送者(控制器)与执行者(
DeviceClient)完全分离,发送者无需知道指令如何执行; - 异步化:通过线程池实现指令异步执行,提升系统响应速度;
- 可扩展:新增 “清库存指令” 只需添加
StockClearCommand类,无需修改调用者代码; - 可追踪:每个指令有唯一 ID,便于日志记录和问题排查。
四、 设计模式选型核心思路
- 先解决问题,再谈模式:不要为了用模式而用模式,简单场景(如查询用户)直接写代码即可;
- 遵循设计原则:所有模式的本质是落地七大设计原则,违背原则的模式使用都是 “过度设计”;
- 组合使用模式:实际项目中,模式往往是组合使用的(如工厂模式 + 策略模式、代理模式 + 装饰器模式);
- 参考框架源码:Spring、MyBatis 等框架是设计模式的最佳实践教材,阅读源码能加深对模式的理解。
五、 总结
设计模式不是 “银弹”,而是解决特定问题的工具。在实际项目中,判断是否使用模式的核心标准是:
- 是否降低了耦合?
- 是否提升了扩展性?
- 是否便于维护和测试?
掌握设计模式的关键,不是死记硬背代码结构,而是理解模式背后的设计思想——高内聚、低耦合、开闭原则,这样才能在面对复杂业务场景时,灵活选择合适的模式。