背景介绍
在电商项目中,积分系统是一个重要的用户留存和激励机制。不同场景下的积分发放逻辑各不相同,如何优雅地组织这些业务逻辑是个典型的设计模式应用场景。策略模式是我在工作中使用最多的设计模式之一,本文将通过一个实际的电商积分发放功能来展示如何应用策略模式。
项目包结构
首先看一下项目的包结构:
com.docobuy.app.service.strategy.point
├── impl/
│ ├── FirstOrderPointAwardStrategy.java // 首单奖励积分策略
│ ├── InvitePointAwardStrategy.java // 邀请新用户奖励积分策略
│ ├── ProfilePointAwardStrategy.java // 完善资料奖励积分策略
│ ├── RegisterPointAwardStrategy.java // 注册奖励积分策略
│ └── RealNameAuthPointAwardStrategy.java // 实名认证奖励积分策略
├── AbstractPointAwardStrategy.java // 抽象策略类
├── PointAwardContext.java // 上下文类
├── PointAwardResult.java // 结果封装类
├── PointAwardService.java // 对外服务类
├── PointAwardStrategy.java // 策略接口
└── PointAwardStrategyFactory.java // 策略工厂类
策略模式的核心组件
1. 策略接口 (PointAwardStrategy)
策略接口定义了所有具体策略类需要实现的方法,是整个策略模式的核心:
public interface PointAwardStrategy {
// 获取策略类型
PointConfigTypeEnum getStrategyType();
// 发放积分
PointAwardResult awardPoints(PointAwardContext context);
// 检查是否可以发放积分
boolean canAward(PointAwardContext context);
}
2. 上下文类 (PointAwardContext)
上下文类用于封装策略执行时需要的各种参数:
@Data
@Builder
public class PointAwardContext {
// 用户ID
private Integer memberId;
// 积分配置键
private String configKey;
// 业务ID(可选,如订单ID、邀请记录ID等)
private String businessId;
// 扩展参数
private Map<String, Object> params;
// 操作描述
private String description;
}
3. 结果封装类 (PointAwardResult)
统一的结果返回格式,提高了代码的一致性:
@Data
@Builder
public class PointAwardResult {
// 是否成功
private boolean success;
// 发放的积分数量
private Integer pointAmount;
// 消息描述
private String message;
// 积分记录ID
private Long recordId;
// 静态工厂方法 - 成功
public static PointAwardResult success(Integer pointAmount, Long recordId, String message) {
// 实现略
}
// 静态工厂方法 - 失败
public static PointAwardResult failed(String message) {
// 实现略
}
}
4. 抽象策略类 (AbstractPointAwardStrategy)
封装各个具体策略的共同代码,减少重复实现:
@Slf4j
public abstract class AbstractPointAwardStrategy implements PointAwardStrategy {
@Autowired
protected PointRecordService pointRecordService;
@Autowired
protected MemberService memberService;
@Override
public PointAwardResult awardPoints(PointAwardContext context) {
// 检查是否可以发放
if (!canAward(context)) {
return PointAwardResult.failed("不满足积分发放条件");
}
// 执行具体策略的积分发放逻辑
return doAwardPoints(context);
}
// 具体的积分发放逻辑由子类实现
protected abstract PointAwardResult doAwardPoints(PointAwardContext context);
// 共用方法:获取用户手机号
protected String getMemberMobile(Integer memberId) {
// 实现略
}
// 共用方法:创建积分记录
protected PointRecord createPointRecord(PointAwardContext context, PointConfig config, Integer pointAmount) {
// 实现略
}
// 策略类型由子类提供
public abstract PointConfigTypeEnum getStrategyType();
}
5. 策略工厂 (PointAwardStrategyFactory)
负责在系统启动时收集并注册所有策略实现,并在运行时根据需要提供合适的策略实例:
@Slf4j
@Component
public class PointAwardStrategyFactory {
private final Map<PointConfigTypeEnum, PointAwardStrategy> strategyMap = new HashMap<>();
@Autowired
private List<PointAwardStrategy> strategies;
@PostConstruct
public void init() {
// 自动注册所有策略实现
for (PointAwardStrategy strategy : strategies) {
strategyMap.put(strategy.getStrategyType(), strategy);
log.info("注册积分策略: {} -> {}", strategy.getStrategyType(), strategy.getClass().getSimpleName());
}
}
// 根据类型获取策略
public PointAwardStrategy getStrategy(PointConfigTypeEnum configType) {
// 实现略
}
// 根据配置键获取策略
public PointAwardStrategy getStrategy(String configKey) {
// 实现略
}
}
6. 对外服务类 (PointAwardService)
对外提供统一的积分发放服务,屏蔽内部策略选择细节:
@Slf4j
@Service
@RequiredArgsConstructor
public class PointAwardService {
private final PointAwardStrategyFactory strategyFactory;
// 发放积分的方法
public PointAwardResult awardPoints(Integer memberId, String configKey, String businessId,
String description, Map<String, Object> params) {
try {
// 构建上下文
PointAwardContext context = PointAwardContext.builder()
.memberId(memberId)
.configKey(configKey)
.businessId(businessId)
.description(description)
.params(params)
.build();
// 获取并执行对应的策略
PointAwardStrategy strategy = strategyFactory.getStrategy(configKey);
return strategy.awardPoints(context);
} catch (Exception e) {
log.error("积分发放异常,用户ID: {}, 配置键: {}", memberId, configKey, e);
return PointAwardResult.failed("系统异常: " + e.getMessage());
}
}
// 简化版积分发放方法
public PointAwardResult awardPoints(Integer memberId, String configKey) {
return awardPoints(memberId, configKey, null, null, null);
}
}
具体策略实现示例
首单奖励积分策略 (FirstOrderPointAwardStrategy)
@Slf4j
@Component
@RequiredArgsConstructor
public class FirstOrderPointAwardStrategy extends AbstractPointAwardStrategy {
@Override
public PointConfigTypeEnum getStrategyType() {
return PointConfigTypeEnum.FIRST_ORDER;
}
@Override
public boolean canAward(PointAwardContext context) {
// 检查配置是否启用
// 检查用户是否已经获得过首单积分
// 返回是否可以发放
}
@Override
@Transactional(rollbackFor = Exception.class)
public PointAwardResult doAwardPoints(PointAwardContext context) {
// 使用分布式锁确保并发安全
// 再次检查是否已发放过(双重检查)
// 获取积分配置
// 增加用户积分
// 创建积分记录
// 返回结果
}
}
邀请奖励积分策略 (InvitePointAwardStrategy)
@Slf4j
@Component
@RequiredArgsConstructor
public class InvitePointAwardStrategy extends AbstractPointAwardStrategy {
@Override
public PointConfigTypeEnum getStrategyType() {
return PointConfigTypeEnum.INVITE;
}
@Override
public boolean canAward(PointAwardContext context) {
// 检查配置是否启用
}
@Override
@Transactional(rollbackFor = Exception.class)
public PointAwardResult doAwardPoints(PointAwardContext context) {
// 分别为邀请人和被邀请人发放积分
// 返回结果
}
}
策略模式的优势
- 开闭原则:添加新的积分发放策略只需创建新的策略类,不需要修改现有代码
- 单一职责:每个策略类只负责一种积分发放逻辑,职责清晰
- 可维护性:各策略之间互不干扰,便于独立维护和测试
- 可扩展性:新增积分场景只需添加新的策略实现类
- 代码复用:通过抽象类共享通用代码,减少重复
使用方法
在业务代码中使用积分发放服务非常简单:
@RestController
@RequestMapping("/api/point")
@RequiredArgsConstructor
public class PointController {
private final PointAwardService pointAwardService;
@PostMapping("/award")
public ApiResult<PointAwardResult> awardPoints(@RequestBody PointAwardRequest request) {
PointAwardResult result = pointAwardService.awardPoints(
request.getMemberId(),
request.getConfigKey(),
request.getBusinessId(),
request.getDescription(),
request.getParams()
);
return ApiResult.success(result);
}
}
总结
策略模式在处理多种相似业务逻辑时非常有效,尤其适合这种”一个接口,多种实现”的场景。在本例中,通过策略模式优雅地解决了多种不同积分发放场景的实现问题,提高了代码的可维护性和扩展性。
通过使用Spring的自动注入和工厂模式,我们还实现了策略的自动注册和动态获取,进一步提高了系统的灵活性。这种设计模式不仅适用于积分系统,也可以应用于优惠券发放、消息通知等多种场景。
如果后续需要在策略实现的基础上增加另一个维度的实现,例如根据用户VIP等级在积分领取后额外发放不同奖励,且每种策略对应的奖励逻辑较为复杂,可以考虑结合使用装饰器模式。装饰器模式能够在不修改原有策略类的情况下,动态地为积分策略添加VIP奖励功能,使系统在保持灵活性的同时能够优雅地处理多维度的业务需求组合。这种模式组合既保持了策略模式的开闭原则,又避免了因多维度组合导致的类爆炸问题。