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. 上下文隔离 :通过 UserContext、OrderContext 传递动态数据,避免策略耦合业务对象。
相关推荐
秋饼3 小时前
LLM API密钥动态轮换实战:Spring Vault + Spring AI + 多Provider路由
java·ai·技术分享·后端开发
马良神笔4 小时前
2026 年Rag最近技术前沿总结
后端·python·django
程序猿DD4 小时前
同样10美元,OpenDesign和Command Code的DeepSeek V4.1 Flash额度到底有多少?
后端
福兮说4 小时前
JSON 格式正确,数据就可靠吗?我用一份自编配置检查类型与空值
java·前端·json
高频因子挖掘机5 小时前
A股量化从日线升级到5分钟线:数据量增加后最先暴露哪些工程问题?
后端·github·api
高频因子挖掘机5 小时前
历史行情返回顺序变了,为什么按行号计算指标很危险?
后端·github·api
紧跟先前的步伐6 小时前
Go AI Agent 实战 03|Token、上下文窗口与模型调用成本
java·人工智能·golang
const_qiu6 小时前
第四章 00:开篇 —— 从「会聊天」到「记得住、会干活、能协作」
java·开发语言·人工智能·python
Wang's Blog6 小时前
Java 项目实战: 交友项目 - 个人资料管理的更新接口实现与坑
java·开发语言·交友
团子哒哒6 小时前
数组、切片和Map
后端