策略模式实现不同渠道获得积分发放

 

背景介绍

在电商项目中,积分系统是一个重要的用户留存和激励机制。不同场景下的积分发放逻辑各不相同,如何优雅地组织这些业务逻辑是个典型的设计模式应用场景。策略模式是我在工作中使用最多的设计模式之一,本文将通过一个实际的电商积分发放功能来展示如何应用策略模式。

项目包结构

首先看一下项目的包结构:

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) {
        // 分别为邀请人和被邀请人发放积分
        // 返回结果
    }
}

策略模式的优势

  1. 开闭原则:添加新的积分发放策略只需创建新的策略类,不需要修改现有代码
  2. 单一职责:每个策略类只负责一种积分发放逻辑,职责清晰
  3. 可维护性:各策略之间互不干扰,便于独立维护和测试
  4. 可扩展性:新增积分场景只需添加新的策略实现类
  5. 代码复用:通过抽象类共享通用代码,减少重复

使用方法

在业务代码中使用积分发放服务非常简单:

@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奖励功能,使系统在保持灵活性的同时能够优雅地处理多维度的业务需求组合。这种模式组合既保持了策略模式的开闭原则,又避免了因多维度组合导致的类爆炸问题。