告别if-else臃肿代码!策略模式在业务中的落地实践与底层逻辑剖析

简介: 策略模式是Java后端开发中解决多分支逻辑问题的“利器”,其核心思想是“封装变化、依赖抽象”,通过抽象策略、具体策略、上下文(或工厂)三个核心角色,实现算法的灵活封装与扩展。

在Java后端开发中,我们经常会遇到多分支判断的场景——比如根据不同的支付方式处理订单、根据不同的用户等级计算积分、根据不同的业务类型生成报表等。最初的实现往往是堆砌if-else语句,这种代码不仅可读性差、难以维护,还违反了开闭原则,新增分支时需要修改原有代码,极易引发bug。而策略模式,正是解决这类“多分支判断臃肿代码”的最优方案之一。

本文将从策略模式的核心定义出发,深入剖析其底层设计逻辑,结合3个不同复杂度的真实业务场景(支付方式适配、会员积分计算、动态规则校验),提供可直接编译运行的完整代码实现,同时解答开发中关于策略模式选型、与其他模式区别、性能优化等关键问题,帮你真正掌握策略模式的落地技巧。

一、策略模式核心认知:是什么、为什么用、底层逻辑

1.1 策略模式的定义

策略模式(Strategy Pattern)是一种行为型设计模式,其核心思想是:定义一组算法(策略),将每个算法封装起来,使它们可以相互替换,并且算法的变化不会影响使用算法的客户端

换句话说,策略模式就是把复杂业务场景中的不同处理逻辑(策略)抽离出来,独立封装成一个个策略类,客户端通过统一的入口选择不同的策略执行,从而避免多分支判断。

1.2 为什么需要策略模式?—— 解决if-else痛点

我们先看一个典型的“反例”:未使用策略模式的支付方式处理代码。

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

/**
* 未使用策略模式的支付处理类
* @author ken
*/

@Slf4j
public class PaymentService {

   /**
    * 处理支付
    * @param orderId 订单ID
    * @param amount 支付金额
    * @param payType 支付方式:ALIPAY(支付宝)、WECHAT(微信)、UNIONPAY(银联)
    * @return 支付结果
    */

   public String processPayment(String orderId, BigDecimal amount, String payType) {
       // 校验参数
       if (StringUtils.hasText(orderId, "订单ID不能为空")) {
           throw new IllegalArgumentException("订单ID不能为空");
       }
       if (ObjectUtils.isEmpty(amount) || amount.compareTo(BigDecimal.ZERO) <= 0) {
           log.error("支付金额非法,订单ID:{},金额:{}", orderId, amount);
           throw new IllegalArgumentException("支付金额必须大于0");
       }
       if (StringUtils.hasText(payType, "支付方式不能为空")) {
           throw new IllegalArgumentException("支付方式不能为空");
       }

       // 多分支判断处理不同支付方式
       if ("ALIPAY".equals(payType)) {
           log.info("执行支付宝支付,订单ID:{},金额:{}", orderId, amount);
           // 支付宝支付逻辑...
           return "支付宝支付成功,订单ID:" + orderId;
       } else if ("WECHAT".equals(payType)) {
           log.info("执行微信支付,订单ID:{},金额:{}", orderId, amount);
           // 微信支付逻辑...
           return "微信支付成功,订单ID:" + orderId;
       } else if ("UNIONPAY".equals(payType)) {
           log.info("执行银联支付,订单ID:{},金额:{}", orderId, amount);
           // 银联支付逻辑...
           return "银联支付成功,订单ID:" + orderId;
       } else {
           log.error("不支持的支付方式,订单ID:{},支付方式:{}", orderId, payType);
           throw new UnsupportedOperationException("不支持的支付方式:" + payType);
       }
   }
}

这段代码存在明显问题:

  1. 可读性差:随着支付方式增加(比如新增ApplePay、GooglePay),if-else分支会持续膨胀,代码冗长混乱;
  2. 可维护性差:修改某一种支付逻辑时,需要直接修改processPayment方法,违反“开闭原则”(对扩展开放、对修改关闭);
  3. 可测试性差:一个方法包含多种逻辑,单元测试需要覆盖所有分支,测试用例复杂;
  4. 耦合度高:支付逻辑与分支判断逻辑强耦合,不利于代码复用。

而使用策略模式重构后,上述问题将得到彻底解决。

1.3 策略模式的底层逻辑与核心角色

策略模式的底层逻辑是基于“封装变化”和“依赖倒置”原则,通过将可变的算法(策略)封装为独立类,使客户端依赖于抽象策略而非具体实现,从而实现算法的灵活替换和扩展。

策略模式包含3个核心角色:

  1. 抽象策略(Strategy):定义策略的统一接口,声明所有具体策略都需要实现的核心方法(如支付方法);
  2. 具体策略(ConcreteStrategy):实现抽象策略接口,封装具体的算法逻辑(如支付宝支付、微信支付的具体实现);
  3. 策略上下文(Context):作为客户端与策略的中间层,负责持有和管理策略对象,提供统一的入口供客户端调用策略,避免客户端直接与具体策略耦合。

其核心交互流程如下:

image.png

通过这一结构,客户端只需通过上下文指定策略类型,无需关注具体策略的实现细节;新增策略时,只需实现抽象策略接口并注册到上下文,无需修改原有代码,完美符合开闭原则。

二、策略模式的基础实现:支付方式适配场景

下面以“多支付方式适配”为例,展示策略模式的基础实现流程,代码基于JDK 17、Spring Boot 3.2.0、Lombok 1.18.30实现,确保可直接编译运行。

2.1 步骤1:定义抽象策略接口(支付策略)

抽象策略接口定义所有支付方式的统一规范,声明支付核心方法。

package com.jam.demo.strategy.payment;

import java.math.BigDecimal;

/**
* 支付策略抽象接口
* @author ken
*/

public interface PaymentStrategy {

   /**
    * 获取支付方式编码(如ALIPAY、WECHAT)
    * @return 支付方式编码
    */

   String getPayType();

   /**
    * 执行支付
    * @param orderId 订单ID
    * @param amount 支付金额
    * @return 支付结果
    */

   String pay(String orderId, BigDecimal amount);
}

2.2 步骤2:实现具体策略类(不同支付方式)

分别实现支付宝、微信、银联三种支付方式的具体逻辑,每个类专注于自身的支付算法。

2.2.1 支付宝支付策略

package com.jam.demo.strategy.payment;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;

/**
* 支付宝支付具体策略
* @author ken
*/

@Slf4j
@Component // 交给Spring管理,便于后续自动注入
public class AlipayStrategy implements PaymentStrategy {

   @Override
   public String getPayType() {
       return "ALIPAY";
   }

   @Override
   public String pay(String orderId, BigDecimal amount) {
       // 模拟支付宝支付核心逻辑:调用支付宝SDK、签名验证、发起支付等
       log.info("【支付宝支付】开始处理订单支付,订单ID:{},支付金额:{}", orderId, amount);
       // 此处可添加真实的支付宝支付SDK调用逻辑
       log.info("【支付宝支付】订单支付成功,订单ID:{}", orderId);
       return "支付宝支付成功,订单ID:" + orderId + ",支付金额:" + amount;
   }
}

2.2.2 微信支付策略

package com.jam.demo.strategy.payment;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;

/**
* 微信支付具体策略
* @author ken
*/

@Slf4j
@Component
public class WechatPayStrategy implements PaymentStrategy {

   @Override
   public String getPayType() {
       return "WECHAT";
   }

   @Override
   public String pay(String orderId, BigDecimal amount) {
       // 模拟微信支付核心逻辑
       log.info("【微信支付】开始处理订单支付,订单ID:{},支付金额:{}", orderId, amount);
       // 此处可添加真实的微信支付SDK调用逻辑
       log.info("【微信支付】订单支付成功,订单ID:{}", orderId);
       return "微信支付成功,订单ID:" + orderId + ",支付金额:" + amount;
   }
}

2.2.3 银联支付策略

package com.jam.demo.strategy.payment;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;

/**
* 银联支付具体策略
* @author ken
*/

@Slf4j
@Component
public class UnionPayStrategy implements PaymentStrategy {

   @Override
   public String getPayType() {
       return "UNIONPAY";
   }

   @Override
   public String pay(String orderId, BigDecimal amount) {
       // 模拟银联支付核心逻辑
       log.info("【银联支付】开始处理订单支付,订单ID:{},支付金额:{}", orderId, amount);
       // 此处可添加真实的银联支付SDK调用逻辑
       log.info("【银联支付】订单支付成功,订单ID:{}", orderId);
       return "银联支付成功,订单ID:" + orderId + ",支付金额:" + amount;
   }
}

2.3 步骤3:实现策略上下文(支付上下文)

策略上下文负责管理所有具体策略,提供统一的支付入口,客户端通过上下文调用对应策略的支付方法。这里利用Spring的自动注入特性,将所有PaymentStrategy实现类注入到Map中,key为支付方式编码,value为具体策略对象,实现策略的自动注册和快速获取。

package com.jam.demo.strategy.payment;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* 支付策略上下文
* 负责管理支付策略,提供统一支付入口
* @author ken
*/

@Slf4j
@Component
public class PaymentContext {

   /**
    * 存储所有支付策略,key:支付方式编码(如ALIPAY),value:对应支付策略对象
    * 使用ConcurrentHashMap保证线程安全
    */

   private final Map<String, PaymentStrategy> paymentStrategyMap;

   /**
    * 构造方法自动注入所有PaymentStrategy实现类,Spring会将所有实现类放入Map中
    * key为getPayType()返回的支付方式编码,value为策略对象
    * @param strategies 所有支付策略实现类
    */

   @Autowired
   public PaymentContext(Map<String, PaymentStrategy> strategies) {
       this.paymentStrategyMap = new ConcurrentHashMap<>(strategies);
       log.info("初始化支付策略Map,加载策略数量:{},策略详情:{}", strategies.size(), strategies.keySet());
   }

   /**
    * 统一支付入口
    * @param orderId 订单ID
    * @param amount 支付金额
    * @param payType 支付方式编码
    * @return 支付结果
    */

   public String processPayment(String orderId, BigDecimal amount, String payType) {
       // 参数校验
       if (!StringUtils.hasText(orderId)) {
           throw new IllegalArgumentException("订单ID不能为空");
       }
       if (ObjectUtils.isEmpty(amount) || amount.compareTo(BigDecimal.ZERO) <= 0) {
           log.error("支付金额非法,订单ID:{},金额:{}", orderId, amount);
           throw new IllegalArgumentException("支付金额必须大于0");
       }
       if (!StringUtils.hasText(payType)) {
           throw new IllegalArgumentException("支付方式不能为空");
       }

       // 根据支付方式获取对应的策略
       PaymentStrategy strategy = paymentStrategyMap.get(payType);
       if (ObjectUtils.isEmpty(strategy)) {
           log.error("不支持的支付方式,订单ID:{},支付方式:{}", orderId, payType);
           throw new UnsupportedOperationException("不支持的支付方式:" + payType);
       }

       // 调用策略的支付方法
       return strategy.pay(orderId, amount);
   }
}

2.4 步骤4:客户端调用(Controller层)

客户端(这里是Controller)通过调用策略上下文的统一入口,传入支付方式参数,即可完成对应支付方式的调用,无需关注具体策略的实现。

package com.jam.demo.controller;

import com.jam.demo.strategy.payment.PaymentContext;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.math.BigDecimal;

/**
* 支付控制器(客户端调用入口)
* @author ken
*/

@Slf4j
@RestController
@RequestMapping("/payment")
@Tag(name = "支付接口", description = "多支付方式适配接口")
public class PaymentController {

   @Autowired
   private PaymentContext paymentContext;

   /**
    * 处理支付请求
    * @param orderId 订单ID
    * @param amount 支付金额
    * @param payType 支付方式:ALIPAY(支付宝)、WECHAT(微信)、UNIONPAY(银联)
    * @return 支付结果
    */

   @PostMapping("/process")
   @Operation(summary = "处理支付", description = "根据传入的支付方式,调用对应支付策略完成支付")
   @Parameters({
           @Parameter(name = "orderId", description = "订单ID", required = true, schema = @Schema(type = "string")),
           @Parameter(name = "amount", description = "支付金额(大于0)", required = true, schema = @Schema(type = "number", format = "decimal")),
           @Parameter(name = "payType", description = "支付方式编码", required = true, schema = @Schema(type = "string", allowableValues = {"ALIPAY", "WECHAT", "UNIONPAY"}))
   })
   @ApiResponses({
           @ApiResponse(responseCode = "200", description = "支付成功", content = @Content(schema = @Schema(type = "string"))),
           @ApiResponse(responseCode = "400", description = "参数非法", content = @Content(schema = @Schema(type = "string"))),
           @ApiResponse(responseCode = "500", description = "服务器内部错误", content = @Content(schema = @Schema(type = "string")))
   })
   public String processPayment(
           @RequestParam String orderId,
           @RequestParam BigDecimal amount,
           @RequestParam String payType
   )
{
       log.info("收到支付请求,订单ID:{},金额:{},支付方式:{}", orderId, amount, payType);
       return paymentContext.processPayment(orderId, amount, payType);
   }
}

2.5 步骤5:配置依赖(pom.xml)

确保项目引入必要的依赖,包括Spring Boot、Lombok、Swagger3等,版本使用最新稳定版。

<?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 http://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/> <!-- lookup parent from repository -->
   </parent>
   <groupId>com.jam.demo</groupId>
   <artifactId>strategy-pattern-demo</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <name>strategy-pattern-demo</name>
   <description>Demo project for Strategy Pattern in Java</description>

   <properties>
       <java.version>17</java.version>
       <fastjson2.version>2.0.32</fastjson2.version>
       <guava.version>32.1.3-jre</guava.version>
   </properties>

   <dependencies>
       <!-- Spring Boot Web -->
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
       </dependency>

       <!-- Lombok -->
       <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
           <version>1.18.30</version>
           <scope>provided</scope>
       </dependency>

       <!-- Swagger3 (SpringDoc OpenAPI) -->
       <dependency>
           <groupId>org.springdoc</groupId>
           <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
           <version>2.2.0</version>
       </dependency>

       <!-- FastJSON2 -->
       <dependency>
           <groupId>com.alibaba.fastjson2</groupId>
           <artifactId>fastjson2</artifactId>
           <version>${fastjson2.version}</version>
       </dependency>

       <!-- Guava (集合工具类) -->
       <dependency>
           <groupId>com.google.guava</groupId>
           <artifactId>guava</artifactId>
           <version>${guava.version}</version>
       </dependency>

       <!-- Spring Boot Test -->
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-test</artifactId>
           <scope>test</scope>
       </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>

2.6 测试验证

启动项目后,访问Swagger3文档地址:http://localhost:8080/swagger-ui/index.html,可通过Swagger界面直接测试接口:

  1. 测试支付宝支付:orderId=ORDER20240501001,amount=100.00,payType=ALIPAY,返回“支付宝支付成功...”;
  2. 测试微信支付:orderId=ORDER20240501002,amount=200.00,payType=WECHAT,返回“微信支付成功...”;
  3. 测试不支持的支付方式:payType=APPLEPAY,将抛出“不支持的支付方式”异常。

2.7 新增策略的扩展方式

如果需要新增“ApplePay支付”,只需新增一个实现PaymentStrategy接口的类,无需修改任何原有代码:

package com.jam.demo.strategy.payment;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;

/**
* ApplePay支付具体策略(新增策略)
* @author ken
*/

@Slf4j
@Component
public class ApplePayStrategy implements PaymentStrategy {

   @Override
   public String getPayType() {
       return "APPLEPAY";
   }

   @Override
   public String pay(String orderId, BigDecimal amount) {
       log.info("【ApplePay支付】开始处理订单支付,订单ID:{},支付金额:{}", orderId, amount);
       log.info("【ApplePay支付】订单支付成功,订单ID:{}", orderId);
       return "ApplePay支付成功,订单ID:" + orderId + ",支付金额:" + amount;
   }
}

重启项目后,Spring会自动将ApplePayStrategy注入到paymentStrategyMap中,客户端直接传入payType=APPLEPAY即可调用,完全符合开闭原则。

三、策略模式的进阶实现:会员积分计算场景(结合数据库与MyBatis-Plus)

在实际业务中,策略模式往往需要结合数据库实现动态配置(如会员等级规则存储在数据库)。下面以“会员积分计算”为例,展示策略模式结合MyBatis-Plus、数据库的进阶实现,场景需求如下:

  • 不同会员等级(普通会员、白银会员、黄金会员、钻石会员)的积分计算规则不同;
  • 积分规则可通过后台配置修改(存储在数据库),无需修改代码;
  • 计算积分时,需根据用户的会员等级查询对应的规则,执行计算。

3.1 场景分析与核心设计

3.1.1 积分规则说明

会员等级 积分计算规则 额外奖励积分
普通会员 消费金额 × 1 0
白银会员 消费金额 × 1.2 消费金额 ≥ 1000 奖励 50 积分
黄金会员 消费金额 × 1.5 消费金额 ≥ 800 奖励 80 积分
钻石会员 消费金额 × 2.0 消费金额 ≥ 500 奖励 120 积分

3.1.2 核心角色设计

  1. 抽象策略(PointCalculationStrategy):定义积分计算的统一接口;
  2. 具体策略(普通/白银/黄金/钻石会员积分策略):实现抽象接口,结合数据库规则计算积分;
  3. 策略上下文(PointCalculationContext):管理策略,提供统一计算入口;
  4. 数据层(Mapper/Service):通过MyBatis-Plus操作数据库,查询会员等级规则。

3.2 步骤1:数据库设计与初始化(MySQL 8.0)

创建会员等级表(member_level),存储积分规则配置。

3.2.1 建表SQL

CREATE TABLE `member_level` (
 `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
 `level_code` varchar(20) NOT NULL COMMENT '会员等级编码(ORDINARY/SILVER/GOLD/DIAMOND)',
 `level_name` varchar(50) NOT NULL COMMENT '会员等级名称',
 `point_rate` decimal(5,2) NOT NULL COMMENT '积分倍率(消费金额×倍率)',
 `reward_condition` decimal(10,2) DEFAULT NULL COMMENT '额外奖励条件(消费金额≥此值)',
 `reward_points` int DEFAULT 0 COMMENT '额外奖励积分',
 `is_enable` tinyint NOT NULL DEFAULT 1 COMMENT '是否启用(1-启用,0-禁用)',
 `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
 PRIMARY KEY (`id`),
 UNIQUE KEY `uk_level_code` (`level_code`) COMMENT '会员等级编码唯一'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员等级表(积分规则配置)';

3.2.2 初始化数据

INSERT INTO `member_level` (`level_code`, `level_name`, `point_rate`, `reward_condition`, `reward_points`, `is_enable`)
VALUES
('ORDINARY', '普通会员', 1.00, NULL, 0, 1),
('SILVER', '白银会员', 1.20, 1000.00, 50, 1),
('GOLD', '黄金会员', 1.50, 800.00, 80, 1),
('DIAMOND', '钻石会员', 2.00, 500.00, 120, 1);

3.3 步骤2:引入MyBatis-Plus依赖(pom.xml)

在原有依赖基础上,新增MyBatis-Plus和MySQL驱动依赖:

<!-- MyBatis-Plus -->
<dependency>
   <groupId>com.baomidou</groupId>
   <artifactId>mybatis-plus-boot-starter</artifactId>
   <version>3.5.5</version>
</dependency>

<!-- MySQL Driver -->
<dependency>
   <groupId>com.mysql</groupId>
   <artifactId>mysql-connector-j</artifactId>
   <scope>runtime</scope>
</dependency>

3.4 步骤3:配置数据库连接(application.yml)

spring:
 datasource:
   url: jdbc:mysql://localhost:3306/strategy_demo?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
   username: root
   password: root123456
   driver-class-name: com.mysql.cj.jdbc.Driver

# MyBatis-Plus配置
mybatis-plus:
 mapper-locations: classpath:mapper/*.xml
 type-aliases-package: com.jam.demo.entity
 configuration:
   map-underscore-to-camel-case: true # 下划线转驼峰
   log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印SQL日志
 global-config:
   db-config:
     id-type: auto # 主键自增
     logic-delete-field: isDelete # 逻辑删除字段
     logic-delete-value: 1 # 逻辑删除值(1-删除)
     logic-not-delete-value: 0 # 逻辑未删除值(0-未删除)

3.5 步骤4:定义实体类、Mapper、Service(MyBatis-Plus)

3.5.1 实体类(MemberLevel)

package com.jam.demo.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
* 会员等级表实体类
* @author ken
*/

@Data
@TableName("member_level")
public class MemberLevel {

   /**
    * 主键ID
    */

   @TableId(type = IdType.AUTO)
   private Long id;

   /**
    * 会员等级编码(ORDINARY/SILVER/GOLD/DIAMOND)
    */

   private String levelCode;

   /**
    * 会员等级名称
    */

   private String levelName;

   /**
    * 积分倍率(消费金额×倍率)
    */

   private BigDecimal pointRate;

   /**
    * 额外奖励条件(消费金额≥此值)
    */

   private BigDecimal rewardCondition;

   /**
    * 额外奖励积分
    */

   private Integer rewardPoints;

   /**
    * 是否启用(1-启用,0-禁用)
    */

   private Integer isEnable;

   /**
    * 创建时间
    */

   private LocalDateTime createTime;

   /**
    * 更新时间
    */

   private LocalDateTime updateTime;
}

3.5.2 Mapper接口(MemberLevelMapper)

package com.jam.demo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.entity.MemberLevel;
import org.springframework.stereotype.Repository;

/**
* 会员等级表Mapper
* @author ken
*/

@Repository
public interface MemberLevelMapper extends BaseMapper<MemberLevel> {
}

3.5.3 Service接口与实现类

package com.jam.demo.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.jam.demo.entity.MemberLevel;

/**
* 会员等级服务接口
* @author ken
*/

public interface MemberLevelService extends IService<MemberLevel> {

   /**
    * 根据会员等级编码查询启用的等级规则
    * @param levelCode 会员等级编码
    * @return 会员等级规则
    */

   MemberLevel getEnableLevelByCode(String levelCode);
}

package com.jam.demo.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jam.demo.entity.MemberLevel;
import com.jam.demo.mapper.MemberLevelMapper;
import com.jam.demo.service.MemberLevelService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

/**
* 会员等级服务实现类
* @author ken
*/

@Slf4j
@Service
public class MemberLevelServiceImpl extends ServiceImpl<MemberLevelMapper, MemberLevel> implements MemberLevelService {

   @Override
   public MemberLevel getEnableLevelByCode(String levelCode) {
       LambdaQueryWrapper<MemberLevel> queryWrapper = new LambdaQueryWrapper<>();
       queryWrapper.eq(MemberLevel::getLevelCode, levelCode)
               .eq(MemberLevel::getIsEnable, 1); // 只查询启用的规则

       MemberLevel memberLevel = baseMapper.selectOne(queryWrapper);
       if (ObjectUtils.isEmpty(memberLevel)) {
           log.error("未查询到启用的会员等级规则,等级编码:{}", levelCode);
           throw new IllegalArgumentException("未查询到启用的会员等级规则:" + levelCode);
       }
       return memberLevel;
   }
}

3.6 步骤5:定义抽象策略与具体策略

3.6.1 抽象策略接口(PointCalculationStrategy)

package com.jam.demo.strategy.point;

import com.jam.demo.entity.MemberLevel;
import java.math.BigDecimal;

/**
* 积分计算策略抽象接口
* @author ken
*/

public interface PointCalculationStrategy {

   /**
    * 获取会员等级编码(对应member_level表的level_code)
    * @return 会员等级编码
    */

   String getLevelCode();

   /**
    * 计算积分
    * @param consumeAmount 消费金额
    * @param memberLevel 会员等级规则(从数据库查询)
    * @return 最终积分
    */

   Integer calculatePoints(BigDecimal consumeAmount, MemberLevel memberLevel);
}

3.6.2 具体策略实现类

3.6.2.1 普通会员积分策略

package com.jam.demo.strategy.point;

import com.jam.demo.entity.MemberLevel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;

/**
* 普通会员积分计算策略
* 规则:消费金额 × 1,无额外奖励
* @author ken
*/

@Slf4j
@Component
public class OrdinaryMemberPointStrategy implements PointCalculationStrategy {

   @Override
   public String getLevelCode() {
       return "ORDINARY";
   }

   @Override
   public Integer calculatePoints(BigDecimal consumeAmount, MemberLevel memberLevel) {
       log.info("【普通会员积分计算】开始计算积分,消费金额:{},会员等级规则:{}", consumeAmount, memberLevel);
       // 普通会员:消费金额 × 积分倍率(1.00),取整数
       BigDecimal pointAmount = consumeAmount.multiply(memberLevel.getPointRate());
       Integer points = pointAmount.intValue();
       log.info("【普通会员积分计算】计算完成,消费金额:{},积分:{}", consumeAmount, points);
       return points;
   }
}

3.6.2.2 白银会员积分策略

package com.jam.demo.strategy.point;

import com.jam.demo.entity.MemberLevel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import java.math.BigDecimal;

/**
* 白银会员积分计算策略
* 规则:消费金额 × 1.2,消费金额≥1000奖励50积分
* @author ken
*/

@Slf4j
@Component
public class SilverMemberPointStrategy implements PointCalculationStrategy {

   @Override
   public String getLevelCode() {
       return "SILVER";
   }

   @Override
   public Integer calculatePoints(BigDecimal consumeAmount, MemberLevel memberLevel) {
       log.info("【白银会员积分计算】开始计算积分,消费金额:{},会员等级规则:{}", consumeAmount, memberLevel);
       // 基础积分:消费金额 × 积分倍率
       BigDecimal basePoint = consumeAmount.multiply(memberLevel.getPointRate());
       Integer points = basePoint.intValue();

       // 额外奖励积分:满足奖励条件则添加
       BigDecimal rewardCondition = memberLevel.getRewardCondition();
       if (!ObjectUtils.isEmpty(rewardCondition) && consumeAmount.compareTo(rewardCondition) >= 0) {
           points += memberLevel.getRewardPoints();
           log.info("【白银会员积分计算】满足额外奖励条件(消费≥{}),添加奖励积分:{}", rewardCondition, memberLevel.getRewardPoints());
       }

       log.info("【白银会员积分计算】计算完成,消费金额:{},最终积分:{}", consumeAmount, points);
       return points;
   }
}

3.6.2.3 黄金会员积分策略

package com.jam.demo.strategy.point;

import com.jam.demo.entity.MemberLevel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import java.math.BigDecimal;

/**
* 黄金会员积分计算策略
* 规则:消费金额 × 1.5,消费金额≥800奖励80积分
* @author ken
*/

@Slf4j
@Component
public class GoldMemberPointStrategy implements PointCalculationStrategy {

   @Override
   public String getLevelCode() {
       return "GOLD";
   }

   @Override
   public Integer calculatePoints(BigDecimal consumeAmount, MemberLevel memberLevel) {
       log.info("【黄金会员积分计算】开始计算积分,消费金额:{},会员等级规则:{}", consumeAmount, memberLevel);
       // 基础积分
       BigDecimal basePoint = consumeAmount.multiply(memberLevel.getPointRate());
       Integer points = basePoint.intValue();

       // 额外奖励积分
       BigDecimal rewardCondition = memberLevel.getRewardCondition();
       if (!ObjectUtils.isEmpty(rewardCondition) && consumeAmount.compareTo(rewardCondition) >= 0) {
           points += memberLevel.getRewardPoints();
           log.info("【黄金会员积分计算】满足额外奖励条件(消费≥{}),添加奖励积分:{}", rewardCondition, memberLevel.getRewardPoints());
       }

       log.info("【黄金会员积分计算】计算完成,消费金额:{},最终积分:{}", consumeAmount, points);
       return points;
   }
}

3.6.2.4 钻石会员积分策略

package com.jam.demo.strategy.point;

import com.jam.demo.entity.MemberLevel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import java.math.BigDecimal;

/**
* 钻石会员积分计算策略
* 规则:消费金额 × 2.0,消费金额≥500奖励120积分
* @author ken
*/

@Slf4j
@Component
public class DiamondMemberPointStrategy implements PointCalculationStrategy {

   @Override
   public String getLevelCode() {
       return "DIAMOND";
   }

   @Override
   public Integer calculatePoints(BigDecimal consumeAmount, MemberLevel memberLevel) {
       log.info("【钻石会员积分计算】开始计算积分,消费金额:{},会员等级规则:{}", consumeAmount, memberLevel);
       // 基础积分
       BigDecimal basePoint = consumeAmount.multiply(memberLevel.getPointRate());
       Integer points = basePoint.intValue();

       // 额外奖励积分
       BigDecimal rewardCondition = memberLevel.getRewardCondition();
       if (!ObjectUtils.isEmpty(rewardCondition) && consumeAmount.compareTo(rewardCondition) >= 0) {
           points += memberLevel.getRewardPoints();
           log.info("【钻石会员积分计算】满足额外奖励条件(消费≥{}),添加奖励积分:{}", rewardCondition, memberLevel.getRewardPoints());
       }

       log.info("【钻石会员积分计算】计算完成,消费金额:{},最终积分:{}", consumeAmount, points);
       return points;
   }
}

3.7 步骤6:实现策略上下文(PointCalculationContext)

package com.jam.demo.strategy.point;

import com.jam.demo.entity.MemberLevel;
import com.jam.demo.service.MemberLevelService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* 积分计算策略上下文
* @author ken
*/

@Slf4j
@Component
public class PointCalculationContext {

   /**
    * 存储所有积分计算策略
    */

   private final Map<String, PointCalculationStrategy> pointStrategyMap;

   /**
    * 会员等级服务(查询数据库规则)
    */

   private final MemberLevelService memberLevelService;

   /**
    * 构造方法注入策略和服务
    * @param strategies 所有积分计算策略实现类
    * @param memberLevelService 会员等级服务
    */

   @Autowired
   public PointCalculationContext(Map<String, PointCalculationStrategy> strategies, MemberLevelService memberLevelService) {
       this.pointStrategyMap = new ConcurrentHashMap<>(strategies);
       this.memberLevelService = memberLevelService;
       log.info("初始化积分计算策略Map,加载策略数量:{},策略详情:{}", strategies.size(), strategies.keySet());
   }

   /**
    * 统一积分计算入口
    * @param userId 用户ID(用于日志追踪)
    * @param consumeAmount 消费金额
    * @param levelCode 会员等级编码
    * @return 最终积分
    */

   public Integer calculatePoints(String userId, BigDecimal consumeAmount, String levelCode) {
       // 参数校验
       if (!StringUtils.hasText(userId)) {
           throw new IllegalArgumentException("用户ID不能为空");
       }
       if (ObjectUtils.isEmpty(consumeAmount) || consumeAmount.compareTo(BigDecimal.ZERO) <= 0) {
           log.error("消费金额非法,用户ID:{},金额:{}", userId, consumeAmount);
           throw new IllegalArgumentException("消费金额必须大于0");
       }
       if (!StringUtils.hasText(levelCode)) {
           throw new IllegalArgumentException("会员等级编码不能为空");
       }

       // 1. 根据等级编码获取对应的积分策略
       PointCalculationStrategy strategy = pointStrategyMap.get(levelCode);
       if (ObjectUtils.isEmpty(strategy)) {
           log.error("不支持的会员等级积分策略,用户ID:{},等级编码:{}", userId, levelCode);
           throw new UnsupportedOperationException("不支持的会员等级:" + levelCode);
       }

       // 2. 从数据库查询该等级的启用规则
       MemberLevel memberLevel = memberLevelService.getEnableLevelByCode(levelCode);

       // 3. 调用策略计算积分
       return strategy.calculatePoints(consumeAmount, memberLevel);
   }
}

3.8 步骤7:客户端调用(Controller层)

package com.jam.demo.controller;

import com.jam.demo.strategy.point.PointCalculationContext;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.math.BigDecimal;

/**
* 积分计算控制器
* @author ken
*/

@Slf4j
@RestController
@RequestMapping("/points")
@Tag(name = "积分计算接口", description = "不同会员等级的积分计算接口")
public class PointCalculationController {

   @Autowired
   private PointCalculationContext pointCalculationContext;

   /**
    * 计算会员消费积分
    * @param userId 用户ID
    * @param consumeAmount 消费金额
    * @param levelCode 会员等级编码:ORDINARY(普通)、SILVER(白银)、GOLD(黄金)、DIAMOND(钻石)
    * @return 最终积分
    */

   @PostMapping("/calculate")
   @Operation(summary = "计算消费积分", description = "根据用户会员等级和消费金额,计算最终积分")
   @Parameters({
           @Parameter(name = "userId", description = "用户ID", required = true, schema = @Schema(type = "string")),
           @Parameter(name = "consumeAmount", description = "消费金额(大于0)", required = true, schema = @Schema(type = "number", format = "decimal")),
           @Parameter(name = "levelCode", description = "会员等级编码", required = true, schema = @Schema(type = "string", allowableValues = {"ORDINARY", "SILVER", "GOLD", "DIAMOND"}))
   })
   @ApiResponses({
           @ApiResponse(responseCode = "200", description = "计算成功", content = @Content(schema = @Schema(type = "integer"))),
           @ApiResponse(responseCode = "400", description = "参数非法或规则不存在", content = @Content(schema = @Schema(type = "string"))),
           @ApiResponse(responseCode = "500", description = "服务器内部错误", content = @Content(schema = @Schema(type = "string")))
   })
   public Integer calculatePoints(
           @RequestParam String userId,
           @RequestParam BigDecimal consumeAmount,
           @RequestParam String levelCode
   )
{
       log.info("收到积分计算请求,用户ID:{},消费金额:{},会员等级:{}", userId, consumeAmount, levelCode);
       return pointCalculationContext.calculatePoints(userId, consumeAmount, levelCode);
   }
}

3.9 测试验证

  1. 普通会员消费800元:userId=USER001,consumeAmount=800.00,levelCode=ORDINARY → 积分=800×1=800;
  2. 白银会员消费1200元:userId=USER002,consumeAmount=1200.00,levelCode=SILVER → 基础积分=1200×1.2=1440,额外奖励50 → 总积分1490;
  3. 钻石会员消费400元:userId=USER003,consumeAmount=400.00,levelCode=DIAMOND → 基础积分=400×2.0=800,未满足500元奖励条件 → 总积分800;
  4. 黄金会员消费800元:userId=USER004,consumeAmount=800.00,levelCode=GOLD → 基础积分=800×1.5=1200,满足800元奖励条件 → 总积分1280。

3.10 动态调整规则(无需修改代码)

如果需要调整白银会员的积分规则(比如将倍率改为1.3,奖励条件改为≥800元奖励60积分),只需直接修改数据库表member_levellevel_code=SILVER的记录:

UPDATE `member_level`
SET `point_rate` = 1.30, `reward_condition` = 800.00, `reward_points` = 60
WHERE `level_code` = 'SILVER';

修改后无需重启项目,再次调用接口时,策略会自动使用更新后的规则计算积分,实现了规则的动态配置。

四、策略模式的高级实现:动态规则校验场景(结合工厂模式+策略模式)

在更复杂的业务场景中,策略模式常与工厂模式结合使用,进一步优化策略的创建和管理。下面以“动态规则校验”为例,展示策略模式+工厂模式的高级实现,场景需求如下:

  • 不同类型的订单(实物订单、虚拟订单、跨境订单)需要执行不同的校验规则(如实物订单校验物流地址,虚拟订单校验有效期,跨境订单校验关税信息);
  • 校验规则可能动态增减,需要支持灵活扩展;
  • 客户端传入订单类型,自动执行对应的校验逻辑,返回校验结果。

4.1 场景分析与核心设计

4.1.1 校验规则说明

订单类型 校验规则
实物订单(PHYSICAL) 1. 物流地址不能为空;2. 物流地址格式正确;3. 商品库存充足
虚拟订单(VIRTUAL) 1. 订单有效期未过期;2. 虚拟商品激活码存在;3. 用户未重复购买
跨境订单(CROSS_BORDER) 1. 关税信息完整;2. 收件人身份证信息完整;3. 商品符合跨境限购规则

4.1.2 核心设计思路

  1. 策略模式:封装不同订单类型的校验规则(具体策略);
  2. 工厂模式:创建策略工厂,负责根据订单类型创建对应的策略对象,替代上下文直接管理策略的方式,进一步解耦;
  3. 统一结果封装:定义校验结果对象,规范返回格式;
  4. 异常处理:自定义校验异常,统一处理校验失败场景。

4.2 步骤1:定义统一结果对象与自定义异常

4.2.1 校验结果对象(ValidationResult)

package com.jam.demo.vo;

import lombok.Data;
import lombok.experimental.Accessors;

/**
* 规则校验结果VO
* @author ken
*/

@Data
@Accessors(chain = true)
public class ValidationResult {

   /**
    * 校验是否通过
    */

   private boolean pass;

   /**
    * 校验失败信息(pass=false时非空)
    */

   private String errorMsg;

   /**
    * 订单ID
    */

   private String orderId;

   /**
    * 订单类型
    */

   private String orderType;

   /**
    * 成功结果构造方法
    * @param orderId 订单ID
    * @param orderType 订单类型
    * @return 校验结果
    */

   public static ValidationResult success(String orderId, String orderType) {
       return new ValidationResult()
               .setPass(true)
               .setOrderId(orderId)
               .setOrderType(orderType);
   }

   /**
    * 失败结果构造方法
    * @param orderId 订单ID
    * @param orderType 订单类型
    * @param errorMsg 失败信息
    * @return 校验结果
    */

   public static ValidationResult fail(String orderId, String orderType, String errorMsg) {
       return new ValidationResult()
               .setPass(false)
               .setOrderId(orderId)
               .setOrderType(orderType)
               .setErrorMsg(errorMsg);
   }
}

4.2.2 自定义校验异常(ValidationException)

package com.jam.demo.exception;

import lombok.Getter;

/**
* 订单校验异常
* @author ken
*/

@Getter
public class ValidationException extends RuntimeException {

   /**
    * 订单ID
    */

   private final String orderId;

   /**
    * 订单类型
    */

   private final String orderType;

   public ValidationException(String message, String orderId, String orderType) {
       super(message);
       this.orderId = orderId;
       this.orderType = orderType;
   }

   public ValidationException(String message, Throwable cause, String orderId, String orderType) {
       super(message, cause);
       this.orderId = orderId;
       this.orderType = orderType;
   }
}

4.3 步骤2:定义订单实体与抽象策略

4.3.1 订单实体(OrderDTO)

package com.jam.demo.dto;

import lombok.Data;
import lombok.experimental.Accessors;

import java.time.LocalDateTime;
import java.math.BigDecimal;

/**
* 订单DTO(数据传输对象)
* @author ken
*/

@Data
@Accessors(chain = true)
public class OrderDTO {

   /**
    * 订单ID
    */

   private String orderId;

   /**
    * 订单类型:PHYSICAL(实物订单)、VIRTUAL(虚拟订单)、CROSS_BORDER(跨境订单)
    */

   private String orderType;

   /**
    * 物流地址(实物订单必填)
    */

   private String logisticsAddress;

   /**
    * 订单有效期(虚拟订单必填)
    */

   private LocalDateTime validTime;

   /**
    * 虚拟商品激活码(虚拟订单必填)
    */

   private String activationCode;

   /**
    * 关税信息(跨境订单必填)
    */

   private String tariffInfo;

   /**
    * 收件人身份证号(跨境订单必填)
    */

   private String idCard;

   /**
    * 商品ID
    */

   private String productId;

   /**
    * 购买数量
    */

   private Integer quantity;

   /**
    * 商品单价
    */

   private BigDecimal price;
}

4.3.2 抽象校验策略(OrderValidationStrategy)

package com.jam.demo.strategy.validation;

import com.jam.demo.dto.OrderDTO;
import com.jam.demo.vo.ValidationResult;

/**
* 订单校验策略抽象接口
* @author ken
*/

public interface OrderValidationStrategy {

   /**
    * 获取订单类型编码(对应OrderDTO的orderType)
    * @return 订单类型编码
    */

   String getOrderType();

   /**
    * 执行订单校验
    * @param orderDTO 订单数据
    * @return 校验结果
    */

   ValidationResult validate(OrderDTO orderDTO);
}

4.4 步骤3:实现具体校验策略

4.4.1 实物订单校验策略

package com.jam.demo.strategy.validation;

import com.jam.demo.dto.OrderDTO;
import com.jam.demo.vo.ValidationResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

/**
* 实物订单校验策略
* 校验规则:1.物流地址不能为空;2.物流地址格式正确;3.商品库存充足
* @author ken
*/

@Slf4j
@Component
public class PhysicalOrderValidationStrategy implements OrderValidationStrategy {

   @Override
   public String getOrderType() {
       return "PHYSICAL";
   }

   @Override
   public ValidationResult validate(OrderDTO orderDTO) {
       log.info("【实物订单校验】开始校验订单,订单ID:{},订单信息:{}", orderDTO.getOrderId(), orderDTO);
       String orderId = orderDTO.getOrderId();
       String orderType = orderDTO.getOrderType();

       // 1. 校验物流地址不能为空
       if (!StringUtils.hasText(orderDTO.getLogisticsAddress())) {
           log.error("【实物订单校验】失败,物流地址不能为空,订单ID:{}", orderId);
           return ValidationResult.fail(orderId, orderType, "物流地址不能为空");
       }

       // 2. 校验物流地址格式(简单模拟:需包含省、市、区、详细地址)
       String logisticsAddress = orderDTO.getLogisticsAddress();
       if (!(logisticsAddress.contains("省") && logisticsAddress.contains("市") && logisticsAddress.contains("区") && logisticsAddress.length() > 10)) {
           log.error("【实物订单校验】失败,物流地址格式不正确,订单ID:{},地址:{}", orderId, logisticsAddress);
           return ValidationResult.fail(orderId, orderType, "物流地址格式不正确,需包含省、市、区及详细地址");
       }

       // 3. 校验商品库存充足(模拟调用库存服务,此处简化判断)
       boolean stockSufficient = checkStock(orderDTO.getProductId(), orderDTO.getQuantity());
       if (!stockSufficient) {
           log.error("【实物订单校验】失败,商品库存不足,订单ID:{},商品ID:{},所需数量:{}",
                   orderId, orderDTO.getProductId(), orderDTO.getQuantity());
           return ValidationResult.fail(orderId, orderType, "商品库存不足");
       }

       log.info("【实物订单校验】成功,订单ID:{}", orderId);
       return ValidationResult.success(orderId, orderType);
   }

   /**
    * 模拟校验商品库存
    * @param productId 商品ID
    * @param quantity 所需数量
    * @return 库存是否充足
    */

   private boolean checkStock(String productId, Integer quantity) {
       // 实际场景中需调用库存服务查询真实库存,此处简化为:商品ID非空且数量≤100则库存充足
       return StringUtils.hasText(productId) && quantity != null && quantity <= 100;
   }
}

4.4.2 虚拟订单校验策略

package com.jam.demo.strategy.validation;

import com.jam.demo.dto.OrderDTO;
import com.jam.demo.vo.ValidationResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;

/**
* 虚拟订单校验策略
* 校验规则:1.订单有效期未过期;2.虚拟商品激活码存在;3.用户未重复购买
* @author ken
*/

@Slf4j
@Component
public class VirtualOrderValidationStrategy implements OrderValidationStrategy {

   @Override
   public String getOrderType() {
       return "VIRTUAL";
   }

   @Override
   public ValidationResult validate(OrderDTO orderDTO) {
       log.info("【虚拟订单校验】开始校验订单,订单ID:{},订单信息:{}", orderDTO.getOrderId(), orderDTO);
       String orderId = orderDTO.getOrderId();
       String orderType = orderDTO.getOrderType();

       // 1. 校验订单有效期未过期
       LocalDateTime validTime = orderDTO.getValidTime();
       if (ObjectUtils.isEmpty(validTime)) {
           log.error("【虚拟订单校验】失败,订单有效期不能为空,订单ID:{}", orderId);
           return ValidationResult.fail(orderId, orderType, "订单有效期不能为空");
       }
       if (validTime.isBefore(LocalDateTime.now())) {
           log.error("【虚拟订单校验】失败,订单已过期,订单ID:{},有效期:{}", orderId, validTime);
           return ValidationResult.fail(orderId, orderType, "订单已过期,有效期:" + validTime);
       }

       // 2. 校验虚拟商品激活码存在
       String activationCode = orderDTO.getActivationCode();
       if (!StringUtils.hasText(activationCode)) {
           log.error("【虚拟订单校验】失败,虚拟商品激活码不能为空,订单ID:{}", orderId);
           return ValidationResult.fail(orderId, orderType, "虚拟商品激活码不能为空");
       }
       boolean activationCodeExists = checkActivationCode(activationCode);
       if (!activationCodeExists) {
           log.error("【虚拟订单校验】失败,虚拟商品激活码不存在,订单ID:{},激活码:{}", orderId, activationCode);
           return ValidationResult.fail(orderId, orderType, "虚拟商品激活码不存在");
       }

       // 3. 校验用户未重复购买(模拟调用用户购买记录服务)
       boolean repeatedPurchase = checkRepeatedPurchase(orderDTO.getProductId());
       if (repeatedPurchase) {
           log.error("【虚拟订单校验】失败,用户已重复购买该虚拟商品,订单ID:{},商品ID:{}", orderId, orderDTO.getProductId());
           return ValidationResult.fail(orderId, orderType, "用户已重复购买该虚拟商品");
       }

       log.info("【虚拟订单校验】成功,订单ID:{}", orderId);
       return ValidationResult.success(orderId, orderType);
   }

   /**
    * 模拟校验激活码存在
    * @param activationCode 激活码
    * @return 激活码是否存在
    */

   private boolean checkActivationCode(String activationCode) {
       // 实际场景中需查询激活码数据库,此处简化为:激活码长度≥8则存在
       return activationCode.length() >= 8;
   }

   /**
    * 模拟校验用户重复购买
    * @param productId 商品ID
    * @return 是否重复购买
    */

   private boolean checkRepeatedPurchase(String productId) {
       // 实际场景中需查询用户购买记录,此处简化为:商品ID为"VIRTUAL001"则视为重复购买
       return "VIRTUAL001".equals(productId);
   }
}

4.4.3 跨境订单校验策略

package com.jam.demo.strategy.validation;

import com.jam.demo.dto.OrderDTO;
import com.jam.demo.vo.ValidationResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.regex.Pattern;

/**
* 跨境订单校验策略
* 校验规则:1.关税信息完整;2.收件人身份证信息完整;3.商品符合跨境限购规则
* @author ken
*/

@Slf4j
@Component
public class CrossBorderOrderValidationStrategy implements OrderValidationStrategy {

   /**
    * 身份证号正则表达式(简单匹配18位)
    */

   private static final Pattern ID_CARD_PATTERN = Pattern.compile("^[1-9]\\d{5}(18|19|20)\\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$");

   @Override
   public String getOrderType() {
       return "CROSS_BORDER";
   }

   @Override
   public ValidationResult validate(OrderDTO orderDTO) {
       log.info("【跨境订单校验】开始校验订单,订单ID:{},订单信息:{}", orderDTO.getOrderId(), orderDTO);
       String orderId = orderDTO.getOrderId();
       String orderType = orderDTO.getOrderType();

       // 1. 校验关税信息完整
       String tariffInfo = orderDTO.getTariffInfo();
       if (!StringUtils.hasText(tariffInfo)) {
           log.error("【跨境订单校验】失败,关税信息不能为空,订单ID:{}", orderId);
           return ValidationResult.fail(orderId, orderType, "关税信息不能为空");
       }
       if (!tariffInfo.contains("关税金额") || !tariffInfo.contains("税率") || !tariffInfo.contains("申报价值")) {
           log.error("【跨境订单校验】失败,关税信息不完整,订单ID:{},关税信息:{}", orderId, tariffInfo);
           return ValidationResult.fail(orderId, orderType, "关税信息不完整,需包含关税金额、税率、申报价值");
       }

       // 2. 校验收件人身份证信息完整
       String idCard = orderDTO.getIdCard();
       if (!StringUtils.hasText(idCard)) {
           log.error("【跨境订单校验】失败,收件人身份证号不能为空,订单ID:{}", orderId);
           return ValidationResult.fail(orderId, orderType, "收件人身份证号不能为空");
       }
       if (!ID_CARD_PATTERN.matcher(idCard).matches()) {
           log.error("【跨境订单校验】失败,收件人身份证号格式不正确,订单ID:{},身份证号:{}", orderId, idCard);
           return ValidationResult.fail(orderId, orderType, "收件人身份证号格式不正确");
       }

       // 3. 校验商品符合跨境限购规则(模拟:单个商品购买数量≤5,订单总金额≤5000元)
       Integer quantity = orderDTO.getQuantity();
       BigDecimal price = orderDTO.getPrice();
       if (quantity == null || quantity > 5) {
           log.error("【跨境订单校验】失败,商品超出限购数量(≤5),订单ID:{},购买数量:{}", orderId, quantity);
           return ValidationResult.fail(orderId, orderType, "商品超出限购数量,单个商品最多购买5件");
       }
       if (ObjectUtils.isEmpty(price)) {
           log.error("【跨境订单校验】失败,商品单价不能为空,订单ID:{}", orderId);
           return ValidationResult.fail(orderId, orderType, "商品单价不能为空");
       }
       BigDecimal totalAmount = price.multiply(new BigDecimal(quantity));
       if (totalAmount.compareTo(new BigDecimal(5000)) > 0) {
           log.error("【跨境订单校验】失败,订单总金额超出跨境限购额度(≤5000元),订单ID:{},总金额:{}", orderId, totalAmount);
           return ValidationResult.fail(orderId, orderType, "订单总金额超出跨境限购额度,最多5000元");
       }

       log.info("【跨境订单校验】成功,订单ID:{}", orderId);
       return ValidationResult.success(orderId, orderType);
   }
}

4.5 步骤4:实现策略工厂(OrderValidationStrategyFactory)

策略工厂负责管理所有具体策略,根据订单类型创建并返回对应的策略对象。此处结合Spring的Bean工厂特性,在工厂初始化时自动加载所有OrderValidationStrategy实现类,存入Map中供后续获取。

package com.jam.demo.strategy.factory;

import com.jam.demo.strategy.validation.OrderValidationStrategy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* 订单校验策略工厂
* 负责根据订单类型获取对应的校验策略
* @author ken
*/

@Slf4j
@Component
public class OrderValidationStrategyFactory implements InitializingBean {

   /**
    * 存储所有订单校验策略,key:订单类型编码,value:对应策略对象
    */

   private final Map<String, OrderValidationStrategy> strategyMap = new ConcurrentHashMap<>();

   /**
    * 自动注入所有OrderValidationStrategy实现类
    */

   @Autowired
   private Map<String, OrderValidationStrategy> strategyBeans;

   /**
    * 根据订单类型获取策略
    * @param orderType 订单类型编码
    * @return 对应的校验策略
    */

   public OrderValidationStrategy getStrategy(String orderType) {
       OrderValidationStrategy strategy = strategyMap.get(orderType);
       if (ObjectUtils.isEmpty(strategy)) {
           log.error("不支持的订单类型校验策略,订单类型:{}", orderType);
           throw new UnsupportedOperationException("不支持的订单类型:" + orderType);
       }
       return strategy;
   }

   /**
    * InitializingBean接口方法,Bean初始化后执行
    * 将注入的策略Bean按订单类型编码存入strategyMap
    */

   @Override
   public void afterPropertiesSet() throws Exception {
       // 遍历所有策略Bean,将key设置为策略的orderType,覆盖默认的Bean名称
       for (OrderValidationStrategy strategy : strategyBeans.values()) {
           String orderType = strategy.getOrderType();
           strategyMap.put(orderType, strategy);
           log.info("订单校验策略工厂加载策略:订单类型={},策略类={}", orderType, strategy.getClass().getName());
       }
       log.info("订单校验策略工厂初始化完成,共加载策略数量:{}", strategyMap.size());
   }
}

4.6 步骤5:实现服务层(OrderValidationService)

服务层作为客户端与工厂/策略的中间层,负责接收客户端请求,通过工厂获取策略,执行校验逻辑,并处理异常。

package com.jam.demo.service;

import com.jam.demo.dto.OrderDTO;
import com.jam.demo.exception.ValidationException;
import com.jam.demo.strategy.factory.OrderValidationStrategyFactory;
import com.jam.demo.strategy.validation.OrderValidationStrategy;
import com.jam.demo.vo.ValidationResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

/**
* 订单校验服务
* @author ken
*/

@Slf4j
@Service
public class OrderValidationService {

   @Autowired
   private OrderValidationStrategyFactory strategyFactory;

   /**
    * 执行订单校验
    * @param orderDTO 订单数据
    * @return 校验结果
    */

   @Transactional(rollbackFor = Exception.class)
   public ValidationResult validateOrder(OrderDTO orderDTO)
{
       // 基础参数校验
       if (ObjectUtils.isEmpty(orderDTO)) {
           log.error("订单校验失败,订单数据不能为空");
           throw new IllegalArgumentException("订单数据不能为空");
       }
       if (!StringUtils.hasText(orderDTO.getOrderId())) {
           log.error("订单校验失败,订单ID不能为空");
           throw new IllegalArgumentException("订单ID不能为空");
       }
       if (!StringUtils.hasText(orderDTO.getOrderType())) {
           log.error("订单校验失败,订单类型不能为空,订单ID:{}", orderDTO.getOrderId());
           throw new IllegalArgumentException("订单类型不能为空");
       }

       try {
           // 1. 通过工厂获取对应策略
           OrderValidationStrategy strategy = strategyFactory.getStrategy(orderDTO.getOrderType());
           // 2. 执行校验
           return strategy.validate(orderDTO);
       } catch (UnsupportedOperationException e) {
           log.error("订单校验失败,订单ID:{},订单类型:{}", orderDTO.getOrderId(), orderDTO.getOrderType(), e);
           return ValidationResult.fail(orderDTO.getOrderId(), orderDTO.getOrderType(), e.getMessage());
       } catch (Exception e) {
           log.error("订单校验异常,订单ID:{},订单类型:{}", orderDTO.getOrderId(), orderDTO.getOrderType(), e);
           // 抛出自定义异常,便于全局异常处理
           throw new ValidationException("订单校验异常:" + e.getMessage(), e, orderDTO.getOrderId(), orderDTO.getOrderType());
       }
   }
}

4.7 步骤6:客户端调用(Controller层)

package com.jam.demo.controller;

import com.jam.demo.dto.OrderDTO;
import com.jam.demo.service.OrderValidationService;
import com.jam.demo.vo.ValidationResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* 订单校验控制器
* @author ken
*/

@Slf4j
@RestController
@RequestMapping("/order/validation")
@Tag(name = "订单校验接口", description = "不同类型订单的动态规则校验接口")
public class OrderValidationController {

   @Autowired
   private OrderValidationService orderValidationService;

   /**
    * 执行订单校验
    * @param orderDTO 订单数据
    * @return 校验结果
    */

   @PostMapping
   @Operation(summary = "执行订单校验", description = "根据订单类型自动匹配对应的校验策略,执行规则校验")
   @ApiResponses({
           @ApiResponse(responseCode = "200", description = "校验完成(成功/失败)", content = @Content(schema = @Schema(implementation = ValidationResult.class))),
           @ApiResponse(responseCode
= "400", description = "参数非法", content = @Content(schema = @Schema(type = "string"))),
           @ApiResponse(responseCode = "500", description = "服务器内部错误", content = @Content(schema = @Schema(type = "string")))
   })
   public ValidationResult validateOrder(@RequestBody OrderDTO orderDTO) {
       log.info("收到订单校验请求,订单ID:{},订单类型:{}", orderDTO.getOrderId(), orderDTO.getOrderType());
       return orderValidationService.validateOrder(orderDTO);
   }
}

4.8 步骤7:全局异常处理(GlobalExceptionHandler)

统一处理校验过程中抛出的异常,返回规范的错误响应。

package com.jam.demo.exception;

import com.jam.demo.vo.ValidationResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
* 全局异常处理器
* @author ken
*/

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

   /**
    * 处理订单校验异常
    * @param e 校验异常
    * @return 校验失败结果
    */

   @ExceptionHandler(ValidationException.class)
   @ResponseStatus(HttpStatus.BAD_REQUEST)
   public ValidationResult handleValidationException(ValidationException e)
{
       log.error("订单校验异常:{},订单ID:{},订单类型:{}", e.getMessage(), e.getOrderId(), e.getOrderType(), e);
       return ValidationResult.fail(e.getOrderId(), e.getOrderType(), e.getMessage());
   }

   /**
    * 处理参数非法异常
    * @param e 参数异常
    * @return 校验失败结果
    */

   @ExceptionHandler(IllegalArgumentException.class)
   @ResponseStatus(HttpStatus.BAD_REQUEST)
   public ValidationResult handleIllegalArgumentException(IllegalArgumentException e)
{
       log.error("参数非法:{}", e.getMessage(), e);
       return ValidationResult.fail(null, null, e.getMessage());
   }

   /**
    * 处理不支持的操作异常
    * @param e 不支持操作异常
    * @return 校验失败结果
    */

   @ExceptionHandler(UnsupportedOperationException.class)
   @ResponseStatus(HttpStatus.BAD_REQUEST)
   public ValidationResult handleUnsupportedOperationException(UnsupportedOperationException e)
{
       log.error("不支持的操作:{}", e.getMessage(), e);
       return ValidationResult.fail(null, null, e.getMessage());
   }

   /**
    * 处理通用异常
    * @param e 通用异常
    * @return 校验失败结果
    */

   @ExceptionHandler(Exception.class)
   @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
   public ValidationResult handleException(Exception e)
{
       log.error("服务器内部错误:{}", e.getMessage(), e);
       return ValidationResult.fail(null, null, "服务器内部错误:" + e.getMessage());
   }
}

4.9 测试验证

通过Postman或Swagger3调用/order/validation接口,传入不同类型的订单数据,验证校验逻辑:

4.9.1 测试1:实物订单(物流地址正确,库存充足)

请求体:

{
 "orderId": "PHYSICAL20240501001",
 "orderType": "PHYSICAL",
 "logisticsAddress": "广东省深圳市南山区科技园XX路XX号",
 "productId": "PHYSICAL001",
 "quantity": 2,
 "price": 100.00
}

响应结果:

{
 "pass": true,
 "errorMsg": null,
 "orderId": "PHYSICAL20240501001",
 "orderType": "PHYSICAL"
}

4.9.2 测试2:虚拟订单(已过期)

请求体:

{
 "orderId": "VIRTUAL20240501001",
 "orderType": "VIRTUAL",
 "validTime": "2023-01-01T12:00:00",
 "activationCode": "VCODE12345678",
 "productId": "VIRTUAL002"
}

响应结果:

{
 "pass": false,
 "errorMsg": "订单已过期,有效期:2023-01-01T12:00",
 "orderId": "VIRTUAL20240501001",
 "orderType": "VIRTUAL"
}

4.9.3 测试3:跨境订单(总金额超出限购)

请求体:

{
 "orderId": "CROSS20240501001",
 "orderType": "CROSS_BORDER",
 "tariffInfo": "关税金额:100元,税率:10%,申报价值:6000元",
 "idCard": "440301199001011234",
 "productId": "CROSS001",
 "quantity": 2,
 "price": 3000.00
}

响应结果:

{
 "pass": false,
 "errorMsg": "订单总金额超出跨境限购额度,最多5000元",
 "orderId": "CROSS20240501001",
 "orderType": "CROSS_BORDER"
}

4.9.4 测试4:不支持的订单类型

请求体:

{
 "orderId": "TEST20240501001",
 "orderType": "TEST",
 "logisticsAddress": "广东省深圳市南山区"
}

响应结果:

{
 "pass": false,
 "errorMsg": "不支持的订单类型:TEST",
 "orderId": "TEST20240501001",
 "orderType": "TEST"
}

4.10 扩展新订单类型校验策略

若需新增“团购订单(GROUP_BUY)”的校验策略,只需新增实现OrderValidationStrategy接口的类,无需修改原有任何代码:

package com.jam.demo.strategy.validation;

import com.jam.demo.dto.OrderDTO;
import com.jam.demo.vo.ValidationResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

/**
* 团购订单校验策略(新增策略)
* 校验规则:1. 团购人数≥2;2. 团购截止时间未过期
* @author ken
*/

@Slf4j
@Component
public class GroupBuyOrderValidationStrategy implements OrderValidationStrategy {

   @Override
   public String getOrderType() {
       return "GROUP_BUY";
   }

   @Override
   public ValidationResult validate(OrderDTO orderDTO) {
       log.info("【团购订单校验】开始校验订单,订单ID:{},订单信息:{}", orderDTO.getOrderId(), orderDTO);
       String orderId = orderDTO.getOrderId();
       String orderType = orderDTO.getOrderType();

       // 此处省略具体校验逻辑(团购人数、截止时间等)
       log.info("【团购订单校验】成功,订单ID:{}", orderId);
       return ValidationResult.success(orderId, orderType);
   }
}

重启项目后,策略工厂会自动加载该策略,客户端传入orderType=GROUP_BUY即可执行团购订单的校验,扩展极为灵活。

五、策略模式的核心优势与适用场景

5.1 核心优势

  1. 彻底解决if-else臃肿问题:将多分支逻辑封装为独立策略类,代码结构清晰,可读性、可维护性大幅提升;
  2. 符合开闭原则:新增策略时无需修改原有代码,只需新增策略类并注册,降低代码变更风险;
  3. 高内聚低耦合:每个策略类专注于自身的算法逻辑,职责单一,策略之间相互独立,降低耦合度;
  4. 灵活性高:支持策略的动态切换(通过上下文或工厂获取不同策略),可根据业务场景动态调整执行逻辑;
  5. 便于测试:每个策略类可独立进行单元测试,测试用例更简洁,覆盖度更高。

5.2 适用场景

  1. 多分支逻辑场景:如多支付方式、多会员等级、多订单类型、多数据校验规则等;
  2. 算法需要动态切换场景:如根据不同场景切换排序算法、加密算法、校验规则等;
  3. 算法逻辑复杂且易扩展场景:如复杂的业务规则计算(积分、返利、计费等),规则可能频繁调整或新增;
  4. 希望降低代码耦合度场景:避免核心业务逻辑与分支判断逻辑强耦合,提升代码复用性。

六、策略模式的常见误区与避坑指南

6.1 常见误区

  1. 过度设计:简单的少量分支逻辑(如2-3个分支)使用策略模式,反而增加代码复杂度;
  2. 策略选择逻辑冗余:在客户端重复编写策略选择逻辑,未通过上下文或工厂统一管理;
  3. 策略对象创建频繁:未对策略对象进行缓存(如Spring管理的单例Bean),导致频繁创建销毁对象,影响性能;
  4. 忽略策略间的依赖关系:多个策略存在依赖时,未合理处理,导致逻辑混乱;
  5. 未处理空策略场景:未考虑不支持的策略类型,未抛出明确异常或返回友好提示。

6.2 避坑指南

  1. 按需使用:分支数量少(≤3)且逻辑简单时,可保留if-else;分支数量多(≥4)或逻辑复杂时,再使用策略模式;
  2. 统一策略管理:通过上下文或工厂集中管理策略选择逻辑,客户端仅需传入策略类型,无需关注选择过程;
  3. 复用策略对象:使用Spring管理策略Bean(单例),或通过工厂缓存策略对象,避免频繁创建;
  4. 明确策略职责:每个策略职责单一,避免策略间的直接依赖,若需依赖可通过服务注入解决;
  5. 完善异常处理:对不支持的策略类型、参数非法等场景,抛出明确的自定义异常,便于问题定位;
  6. 结合其他模式优化:复杂场景可结合工厂模式(优化策略创建)、单例模式(复用策略对象)、享元模式(缓存策略)等。

七、策略模式与其他相似模式的区别

7.1 策略模式 vs 工厂模式

  • 核心目的不同:策略模式专注于“算法的封装与切换”,解决的是“如何执行不同逻辑”的问题;工厂模式专注于“对象的创建”,解决的是“如何创建不同对象”的问题;
  • 角色定位不同:策略模式的核心是“策略接口+具体策略”,客户端通过上下文调用策略的方法;工厂模式的核心是“工厂类+产品类”,客户端通过工厂获取产品对象,再调用产品方法;
  • 使用场景互补:实际开发中常结合使用(如本文第四节),工厂模式负责创建策略对象,策略模式负责执行具体算法,进一步解耦创建与执行逻辑。

7.2 策略模式 vs 状态模式

  • 核心逻辑不同:策略模式中,策略的切换由客户端主动指定(通过传入策略类型);状态模式中,状态的切换由对象的内部状态决定,客户端无需主动干预;
  • 关注点不同:策略模式关注“不同算法的替换”,策略之间相互独立,无依赖;状态模式关注“对象状态的变化及状态对应的行为”,状态之间可能存在依赖(如状态流转);
  • 适用场景不同:策略模式适用于客户端主动选择算法的场景;状态模式适用于对象状态随业务流程自动变化的场景(如订单状态:待支付→已支付→待发货→已发货)。

7.3 策略模式 vs 模板方法模式

  • 核心思想不同:策略模式是“封装不同的算法”,算法的整体逻辑可能完全不同;模板方法模式是“封装固定流程,允许子类替换流程中的特定步骤”,整体流程固定,仅局部步骤可定制;
  • 实现方式不同:策略模式通过接口定义统一方法,具体策略实现接口;模板方法模式通过抽象类定义固定流程(模板方法),子类继承抽象类并实现抽象方法(可定制步骤);
  • 耦合度不同:策略模式中客户端与策略接口耦合,耦合度低;模板方法模式中子类与抽象类的流程耦合,耦合度相对较高。

八、策略模式的性能优化与高级实践

8.1 性能优化

  1. 策略对象缓存:使用Spring单例Bean管理策略对象,或通过工厂将策略对象存入Map缓存,避免频繁创建销毁;
  2. 懒加载策略:对于初始化成本高的策略(如依赖外部资源),可采用懒加载方式,在首次使用时才创建策略对象;
  3. 避免策略过多:若策略数量极多(如数百个),可结合享元模式复用相似策略,或通过动态代理、注解驱动等方式简化策略创建;
  4. 并发安全处理:使用线程安全的容器(如ConcurrentHashMap)存储策略,避免多线程环境下的并发问题。

8.2 高级实践

  1. 注解驱动策略注册:通过自定义注解(如@StrategyType)标记策略类,在项目启动时扫描注解,自动将策略注册到工厂,无需手动注入Map;
  2. 动态配置策略:将策略类型与策略类的映射关系存储在配置中心(如Nacos、Apollo),支持策略的动态上下线,无需重启项目;
  3. 策略链模式结合:多个策略需要按顺序执行时,可结合链模式,将策略组成链条,依次执行(如多步骤数据校验、多规则过滤);
  4. 策略模式与Spring Cloud结合:在微服务架构中,不同策略可部署为独立微服务,通过服务发现动态选择策略服务,实现跨服务的策略扩展。

九、总结

策略模式是Java后端开发中解决多分支逻辑问题的“利器”,其核心思想是“封装变化、依赖抽象”,通过抽象策略、具体策略、上下文(或工厂)三个核心角色,实现算法的灵活封装与扩展。

在实际开发中,使用策略模式时需注意“按需设计”,避免过度设计,同时可结合工厂模式、注解驱动、配置中心等技术进一步优化,提升代码的灵活性和可维护性。掌握策略模式,不仅能解决实际业务中的臃肿代码问题,更能加深对“开闭原则”“依赖倒置原则”等设计原则的理解,提升代码设计能力。

目录
相关文章
|
28天前
|
缓存 NoSQL Java
多级缓存架构实战指南
本文详解如何利用装饰器模式实现多级缓存架构,通过Caffeine、Redis与MySQL三级联动,兼顾高性能与数据一致性。采用SpringBoot实战,代码可落地,有效解决高并发场景下的缓存穿透、击穿、雪崩问题,提升系统稳定性与扩展性。
96 1
|
29天前
|
监控 Java 测试技术
深度解析@Async注解:从实战应用到底层原理,避坑指南全攻略
本文全面解析Spring框架中@Async注解的使用方法和核心原理。文章首先介绍异步调用的概念与适用场景,然后详细讲解@Async的基础使用方式,包括环境搭建、注解配置和返回值处理。重点阐述了自定义线程池的三种实现方案及参数配置最佳实践,并深入剖析了@Async的底层实现机制和动态代理原理。针对实际开发中的常见问题,文章提供了事务处理、异常捕获、批量任务等进阶解决方案,并通过用户注册案例演示了异步任务的实际应用。
196 6
|
1月前
|
网络协议 Java 数据安全/隐私保护
吃透OSI七层模型:从底层逻辑到实战落地,一文打通网络通信任督二脉
本文从“底层逻辑拆解+权威标准解读+可落地实战示例”三个维度,用通俗的语言讲透OSI七层模型的每一个细节。所有内容均参考ISO/IEC 7498-1官方标准(OSI模型的权威定义),核心论点100%有据可依;实战示例基于Java语言实现,确保可直接编译运行;同时针对易混淆技术点进行明确区分,帮你真正做到“知其然,更知其所以然”。
266 2
|
1月前
|
JSON Java 应用服务中间件
ELK Stack(ES+Logstash+Kibana)全链路通关指南
ELK Stack(Elasticsearch、Logstash、Kibana)作为开源领域最成熟的日志与数据分析解决方案,凭借其高可扩展性、实时性和易用性,被阿里、腾讯、字节跳动等大厂广泛应用于日志收集、业务检索、运维监控等场景。
310 3
|
28天前
|
Java 应用服务中间件 数据库
Tomcat底层原理与实战全解析
本文全面解析Tomcat的底层原理与实战应用。作为轻量级JavaEE容器,Tomcat由HTTP服务器和Servlet容器组成,采用分层架构(Server→Service→Engine→Host→Context)。文章详细讲解请求处理流程、安装配置优化、Web应用部署方式,并提供SpringBoot+Tomcat的完整实战案例。针对生产环境,重点介绍性能优化策略(JVM调优、Connector配置)、故障排查工具(jstack、jmap)以及高可用方案(Nginx负载均衡+Redis会话共享)。
172 1
|
28天前
|
SQL 监控 安全
线程池单例模式实现
本文详解Java中单例线程池的设计与实现,剖析静态内部类、双重检查锁和Spring Bean三种生产级方案,解决资源浪费、管控混乱等问题,提升系统并发性能与稳定性。
84 1
|
1月前
|
人工智能 NoSQL 前端开发
Chap03. SpringAI
SpringAI整合主流大模型,支持多模态、函数调用与RAG,提供统一API简化开发。通过ChatClient封装对话流程,结合Prompt工程、工具调用和知识库扩展,可快速构建智能客服、聊天机器人等应用,助力Java开发者高效集成AI能力。
263 0
|
Java Maven
intellij idea如何查看项目maven依赖关系图
这篇文章介绍了如何在IntelliJ IDEA中查看项目的Maven依赖关系图,包括使用Maven工具栏和相关操作来展示和查看依赖细节。
|
设计模式 Java
设计模式--适配器模式 Adapter Pattern
这篇文章介绍了适配器模式,包括其基本介绍、工作原理以及类适配器模式、对象适配器模式和接口适配器模式三种实现方式。
|
Java Spring
Spring Boot 启动报错解决:No active profile set, falling back to default profiles: default
Spring Boot 启动报错解决:No active profile set, falling back to default profiles: default
1011 0