java策略模式实战之优惠券

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

进行实战抽象优惠券的发券与核销逻辑,支持满减券、折扣券、兑换券等不同类型,并具备良好的扩展性。


1. 优惠券类型枚举

java

arduino 复制代码
public enum CouponType {
    FULL_REDUCTION,   // 满减券
    DISCOUNT,         // 折扣券
    EXCHANGE          // 兑换券
}

2. 优惠券模板(CouponTemplate)

模板定义了优惠券的基本属性及规则参数。

java

typescript 复制代码
import java.math.BigDecimal;

public class CouponTemplate {
    private Long templateId;
    private String name;
    private CouponType type;
    private BigDecimal thresholdAmount;   // 满减门槛,仅满减券使用
    private BigDecimal reduceAmount;      // 减金额,仅满减券使用
    private BigDecimal discountRate;      // 折扣率(0-1),仅折扣券使用
    private String exchangeGoodsId;       // 可兑换商品ID,仅兑换券使用

    // getter/setter 省略
}

3. 用户券(UserCoupon)

用户领取后生成的实例,包含状态、使用时间等。

java

kotlin 复制代码
import java.time.LocalDateTime;

public class UserCoupon {
    private Long userCouponId;
    private Long userId;
    private Long templateId;
    private CouponType type;
    private CouponStatus status;        // UNUSED, USED, EXPIRED
    private LocalDateTime expireTime;
    private LocalDateTime useTime;

    // 冗余模板规则字段,便于独立判断(也可以关联模板动态获取)
    private BigDecimal thresholdAmount;
    private BigDecimal reduceAmount;
    private BigDecimal discountRate;
    private String exchangeGoodsId;

    // getter/setter 省略
}

enum CouponStatus {
    UNUSED, USED, EXPIRED
}

4. 策略接口

定义发券(发放时做规则校验、填充用户券信息)和核销(使用时做校验、扣减)两个核心行为。

java

arduino 复制代码
public interface CouponStrategy {

    /**
     * 发放校验:根据模板和用户上下文判断是否可发放
     */
    boolean canIssue(CouponTemplate template, UserContext userContext);

    /**
     * 发放动作:生成用户券,填充特定字段
     */
    UserCoupon issue(CouponTemplate template, UserContext userContext);

    /**
     * 核销校验:使用前验证是否满足规则(如满减金额、折扣商品等)
     */
    boolean canUse(UserCoupon userCoupon, OrderContext orderContext);

    /**
     * 核销动作:计算优惠金额,并更新用户券状态
     */
    BigDecimal use(UserCoupon userCoupon, OrderContext orderContext);
}

5. 上下文对象

为了演示,定义简单上下文。

java

kotlin 复制代码
public class UserContext {
    private Long userId;
    private Integer userLevel;
    // 可根据需要扩展
    // getter/setter 省略
}

public class OrderContext {
    private BigDecimal orderAmount;
    private List<OrderItem> items;
    // 省略 getter/setter
}

public class OrderItem {
    private String goodsId;
    private BigDecimal amount;
    // getter/setter 省略
}

6. 具体策略实现

6.1 满减券策略

java

typescript 复制代码
public class FullReductionStrategy implements CouponStrategy {

    @Override
    public boolean canIssue(CouponTemplate template, UserContext userContext) {
        // 简单示例:无额外限制
        return true;
    }

    @Override
    public UserCoupon issue(CouponTemplate template, UserContext userContext) {
        UserCoupon userCoupon = new UserCoupon();
        userCoupon.setUserId(userContext.getUserId());
        userCoupon.setTemplateId(template.getTemplateId());
        userCoupon.setType(template.getType());
        userCoupon.setThresholdAmount(template.getThresholdAmount());
        userCoupon.setReduceAmount(template.getReduceAmount());
        // 设置过期时间等...
        return userCoupon;
    }

    @Override
    public boolean canUse(UserCoupon userCoupon, OrderContext orderContext) {
        // 满减券:订单金额需满足门槛
        return orderContext.getOrderAmount().compareTo(userCoupon.getThresholdAmount()) >= 0;
    }

    @Override
    public BigDecimal use(UserCoupon userCoupon, OrderContext orderContext) {
        // 核销:直接返回减金额
        return userCoupon.getReduceAmount();
    }
}

6.2 折扣券策略

java

typescript 复制代码
public class DiscountStrategy implements CouponStrategy {

    @Override
    public boolean canIssue(CouponTemplate template, UserContext userContext) {
        return true;
    }

    @Override
    public UserCoupon issue(CouponTemplate template, UserContext userContext) {
        UserCoupon userCoupon = new UserCoupon();
        userCoupon.setUserId(userContext.getUserId());
        userCoupon.setTemplateId(template.getTemplateId());
        userCoupon.setType(template.getType());
        userCoupon.setDiscountRate(template.getDiscountRate());
        return userCoupon;
    }

    @Override
    public boolean canUse(UserCoupon userCoupon, OrderContext orderContext) {
        // 折扣券通常不限制门槛,但可以按需扩展
        return true;
    }

    @Override
    public BigDecimal use(UserCoupon userCoupon, OrderContext orderContext) {
        // 折扣:订单金额 * 折扣率
        BigDecimal discount = orderContext.getOrderAmount()
                .multiply(BigDecimal.ONE.subtract(userCoupon.getDiscountRate()));
        return discount;
    }
}

6.3 兑换券策略

java

typescript 复制代码
public class ExchangeStrategy implements CouponStrategy {

    @Override
    public boolean canIssue(CouponTemplate template, UserContext userContext) {
        return true;
    }

    @Override
    public UserCoupon issue(CouponTemplate template, UserContext userContext) {
        UserCoupon userCoupon = new UserCoupon();
        userCoupon.setUserId(userContext.getUserId());
        userCoupon.setTemplateId(template.getTemplateId());
        userCoupon.setType(template.getType());
        userCoupon.setExchangeGoodsId(template.getExchangeGoodsId());
        return userCoupon;
    }

    @Override
    public boolean canUse(UserCoupon userCoupon, OrderContext orderContext) {
        // 兑换券:检查订单中是否包含对应商品
        return orderContext.getItems().stream()
                .anyMatch(item -> userCoupon.getExchangeGoodsId().equals(item.getGoodsId()));
    }

    @Override
    public BigDecimal use(UserCoupon userCoupon, OrderContext orderContext) {
        // 兑换券:优惠金额等于该商品价格
        return orderContext.getItems().stream()
                .filter(item -> userCoupon.getExchangeGoodsId().equals(item.getGoodsId()))
                .map(OrderItem::getAmount)
                .findFirst()
                .orElse(BigDecimal.ZERO);
    }
}

7. 策略工厂 & 上下文

提供统一的入口,根据券类型获取对应策略。

java

java 复制代码
import java.util.EnumMap;
import java.util.Map;

public class CouponStrategyFactory {
    private static final Map<CouponType, CouponStrategy> STRATEGY_MAP = new EnumMap<>(CouponType.class);

    static {
        STRATEGY_MAP.put(CouponType.FULL_REDUCTION, new FullReductionStrategy());
        STRATEGY_MAP.put(CouponType.DISCOUNT, new DiscountStrategy());
        STRATEGY_MAP.put(CouponType.EXCHANGE, new ExchangeStrategy());
    }

    public static CouponStrategy getStrategy(CouponType type) {
        CouponStrategy strategy = STRATEGY_MAP.get(type);
        if (strategy == null) {
            throw new IllegalArgumentException("Unsupported coupon type: " + type);
        }
        return strategy;
    }
}

8. 使用示例

java

arduino 复制代码
public class CouponService {

    // 发券
    public UserCoupon issueCoupon(CouponTemplate template, UserContext userContext) {
        CouponStrategy strategy = CouponStrategyFactory.getStrategy(template.getType());
        if (!strategy.canIssue(template, userContext)) {
            throw new BusinessException("用户不符合发券条件");
        }
        return strategy.issue(template, userContext);
    }

    // 核销
    public BigDecimal useCoupon(UserCoupon userCoupon, OrderContext orderContext) {
        CouponStrategy strategy = CouponStrategyFactory.getStrategy(userCoupon.getType());
        if (!strategy.canUse(userCoupon, orderContext)) {
            throw new BusinessException("优惠券使用条件不满足");
        }
        BigDecimal discount = strategy.use(userCoupon, orderContext);
        // 更新用户券状态为已使用
        userCoupon.setStatus(CouponStatus.USED);
        // 持久化...
        return discount;
    }
}

设计要点总结

  1. 开闭原则:新增券类型只需添加枚举、实现新策略类,无需修改已有代码。
  2. 单一职责:每种策略只负责一种券的发放与核销逻辑。
  3. 工厂模式:将策略创建集中管理,调用方无需关心具体实现。
  4. 上下文隔离 :通过 UserContextOrderContext 传递动态数据,避免策略耦合业务对象。
相关推荐
心勤则明2 小时前
用 SpringAIAlibab 让高频问题实现毫秒级响应
java·人工智能·spring
anzhxu2 小时前
SpringBoot 3.x 整合swagger
java·spring boot·后端
青椒啊2 小时前
DPDK入门到精通(一)
后端
gechunlian882 小时前
Spring Security 官网文档学习
java·学习·spring
小江的记录本2 小时前
【Bean】JavaBean(原生规范)/ Spring Bean 【重点】/ 企业级Bean(EJB/Jakarta Bean)
java·数据库·spring boot·后端·spring·spring cloud·mybatis
qqty12172 小时前
spring loC&DI 详解
java·spring·rpc
中国胖子风清扬2 小时前
Camunda 8 概念详解:梳理新一代工作流引擎的核心概念与组件
java·spring boot·后端·spring cloud·ai·云原生·spring webflux
闻哥2 小时前
MySQL InnoDB 缓存池(Buffer Pool)详解:原理、结构与链表管理
java·数据结构·数据库·mysql·链表·缓存·面试
前端付豪2 小时前
实现必要的流式输出(Streaming)
前端·后端·agent