策略模式 (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;
}
}
设计要点总结
- 开闭原则:新增券类型只需添加枚举、实现新策略类,无需修改已有代码。
- 单一职责:每种策略只负责一种券的发放与核销逻辑。
- 工厂模式:将策略创建集中管理,调用方无需关心具体实现。
- 上下文隔离 :通过
UserContext、OrderContext传递动态数据,避免策略耦合业务对象。