策略模式实现优美代码

1,723 阅读4分钟

前情提要:

我参与了一个任务调度系统,在处理任务的冲突校验模块时,项目经理在资质冲突、时间冲突的基础上又新增了一个任务数量冲突校验,

本着写易扩展,已维护的代码,所以我使用了策略模式来实现。

数据介绍:

任务配置:

任务配置会根据 jobCode(任务编码)、jobType(任务类型)、company(保障公司)、safeguardType(保障类型)
作为一个唯一值(confKey)来配置数据。

image-20240521151304899.png

最大任务数map (jobConfNumMap)

jobConfNumMap的key -> confKey#航班id value -> 最大任务数

业务流程图:

image-20240521151831112.png

代码实现:

策略模式部分:

定义接口:

@Component
public interface TaskProcessingStrategy {
    void processTask(FlightTaskWithStaffVO vo);
}

实现接口(每个类型的任务都有一个实现类来处理业务):

【系统未预排】

@Slf4j
@Component
public class SystemNotWorkerJobStrategy implements TaskProcessingStrategy {
    @Override
    public void processTask(FlightTaskWithStaffVO vo) {
​
        log.info("处理类型 【系统未预排】 的数据:{}", vo);
​
        // todo 业务
        vo.setIsDelete(true);
    }
}

【手动未预排】

@Slf4j
@Component
public class UserNotWorkerJobStrategy implements TaskProcessingStrategy {
    @Override
    public void processTask(FlightTaskWithStaffVO vo) {
​
        log.info("处理类型 【手动未预排】 的数据:{}", vo);
​
        // todo 业务, 看情况删不删除。
​
        vo.setIsDelete(true);
    }
}

【手动已预排】

@Slf4j
@Component
public class UserHaveWorkerJobStrategy implements TaskProcessingStrategy {
    @Override
    public void processTask(FlightTaskWithStaffVO vo) {
​
        log.info("处理类型 【手动已预排】 的数据:{}", vo);
​
        // 设置数量冲突标识
        vo.setIsConflict(true)
                .setConflictType(new HashSet<>(CollUtil.addAll(vo.getConflictType(), ConflictTypeEnum.JOB_NUM.getCode())))
                .setConflictReason(new HashSet<>(CollUtil.addAll(vo.getConflictReason(), ConflictTypeEnum.JOB_NUM.getDesc())));
                
       // 被标记的冲突后续会被记录到冲突日志中
    }
}

context上下文(用于 任务类型与具体实现类的绑定)

@Component
@Slf4j
public class TaskProcessingContext {
​
    @Autowired
    private ApplicationContext applicationContext;
​
    private Map<String, TaskProcessingStrategy> strategyMap;
​
    @PostConstruct
    private void init() {
    
        // 在初始化时获取 strategyMap, key是实现类的类名
        strategyMap = applicationContext.getBeansOfType(TaskProcessingStrategy.class);
​
        if (strategyMap == null || strategyMap.isEmpty()) {
            log.warn("No TaskProcessingStrategy beans found in the ApplicationContext.");
        } else {
            for (String strategyKey : strategyMap.keySet()) {
                log.info("key: {}" + strategyKey);
            }
        }
    }
​
    // 执行任务处理的方法
    public void processTask(String type, FlightTaskWithStaffVO vo) {
​
        TaskProcessingStrategy strategy = strategyMap.get(type);
        if (strategy != null) {
            strategy.processTask(vo);
        } else {
​
            // 如果没有找到对应类型的策略对象,则抛出异常或者进行其他处理
            log.info("No strategy found for type: {}", type);
        }
    }
}
​

任务类型枚举:code跟实现类的类名相同,方便strategyMap.get() 获取指定的实现类, desc是优先级,用于排序,优先处理优先级低的任务。

@AllArgsConstructor
@Getter
public enum FlightJobTypePriorityEnum {
​
    /**
     * 会从低优先级的类型开始执行
     */
​
    // 系统未预排
    SYSTEM_NOT_WORKER_JOB("systemNotWorkerJobStrategy", 0),
​
    // 用户新增未预排
    USER_NOT_WORKER_JOB("userNotWorkerJobStrategy", 1),
​
    // 用户新增已预排
    USER_HAVE_WORKER_JOB("userHaveWorkerJobStrategy", 2),
    ;
​
    private String code;
    private Integer desc;
    
    // 根据code查询优先级
    public static Integer getDescByCode(String code){
        return Arrays.stream(FlightJobTypePriorityEnum.values()).filter(s-> Objects.equals(code,s.getCode())).map(s -> s.getDesc())
                .findFirst().orElse(99);
    }
}

业务实现: (数据查询部分使用注释说明,主要突出如何使用策略模式)

@Transactional(rollbackFor = Exception.class)
    public void processTasksBusiness(List<FlightTaskWithStaffVO> tasks) {
​
        // 获取全天的数据
        List<FlightTaskWithStaffVO> allTasks;
​
​
        // 获取每个配置的 任务数量上限
        Map<String, Long> jobConfNumMap;
​
​
        // 根据confKey#航班Id来分组
        Map<String, List<FlightTaskWithStaffVO>> allTaskMap = allTasks.stream().filter(f -> ObjUtil.isNotEmpty(f.getSafeguardType())).collect(groupingBy(f -> String.format("%s#%s#%s#%s#%s", f.getJobCode(), f.getJobType(), f.getCompany(), f.getSafeguardType(), f.getFlightCombinationId())));
​
        // 这里可以将map里的逻辑整成一个异步方法,使用虚拟线程调用
        allTaskMap.forEach((k, v) -> {
​
            // 获取数据库数据任务数量
            Long jobNum = Long.valueOf(v.size());
​
            // 获取配置任务数量
            Long jobConfNum = ObjUtil.isNotEmpty(jobConfNumMap.get(k)) ? jobConfNumMap.get(k) : 0L;
​
            // 做对比
            if (jobNum > jobConfNum) {
                log.info("执行业务的key:{}", k);
                Long needHandle = jobNum  - jobConfNum;
​
                // 任务数量业务处理逻辑,策略模式入口在这
                this.processTasks(needHandle.intValue(), v);
            } else if (jobNum < jobConfNum) {
​
                // todo 还原被设置为数量冲突的任务
​
            }
        });
​
        Map<String, FlightTaskWithStaffVO> allTaskByGuidMap = allTasks.stream().collect(toMap(FlightTaskWithStaffVO::getGuid, task -> task));
​
        // 同步数据,tasks是调度范围的已预排的数据,allTasks包含了未预排的数据
        tasks.forEach(task -> {
            FlightTaskWithStaffVO correspondingTask = allTaskByGuidMap.get(task.getGuid());
            if (correspondingTask != null) {
                task.setIsDelete(correspondingTask.getIsDelete());
                task.setIsConflict(correspondingTask.getIsConflict());
                task.setConflictType(correspondingTask.getConflictType());
                task.setConflictReason(correspondingTask.getConflictReason());
            }
        });
​
        // 执行删除业务
        List<String> delIds = allTasks.stream().filter(f -> Boolean.TRUE.equals(f.getIsDelete())).map(WsbFlightJob::getGuid).toList();
        if (CollUtil.isNotEmpty(delIds)) {
            this.lambdaUpdate().set(WsbFlightJob::getIsDelete, true)
                    .set(WsbFlightJob::getRemark, "因超过任务数量上限被删除")
                    .in(WsbFlightJob::getGuid, delIds)
                    .update();
        }
​
        // 被删除的任务不参与进一步的冲突判定
        tasks = tasks.stream().filter(f -> !Boolean.TRUE.equals(f.getIsDelete())).toList();
    }

processTasks(遍历执行业务):

    @Transactional(rollbackFor = Exception.class)
    public void processTasks(Integer differenceValue, List<FlightTaskWithStaffVO> vos) {
​
        final Integer[] N = { differenceValue };
        // 按照优先级从低到高排序
        vos = vos.stream()
                .sorted(Comparator.comparing(FlightTaskWithStaffVO::getFlightJobTypePriority)).toList();
​
        // 遍历N次
        vos.stream().forEachOrdered(task -> {
                    if (N[0] <= 0) {
                        return; // 已经处理了N条数据,退出处理
                    }
​
                    try {
                        String flightJobType = task.getFlightJobType();
                        log.info("type: {}",flightJobType);
                        
                        // 策略模式上下文,寻找对应的实现类,执行业务逻辑
                        taskProcessingContext.processTask(flightJobType ,task);
​
                        N[0]--;
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                });
    }

更新 2024年7月24号(补充优先级,任务类型的判定,在实体中补充)

    // 先判定类型,再根据类型获取优先级
    public Integer getFlightJobTypePriority() {

        if (this.flightJobTypePriority == null) {

            if (this.flightJobType == null) {
                calculateFlightJobType();
            }

            this.flightJobTypePriority = FlightJobTypePriorityEnum.getDescByCode(this.flightJobType);
            return this.flightJobTypePriority;
        } else {
            return this.flightJobTypePriority;
        }
    }

    private void calculateFlightJobType() {
        if (this.getDataSource().equals(FlightJobDataSourceEnum.SYSTEM.getVal()) && ObjUtil.isNull(
                this.getWorkerCode())) {
            this.flightJobType = FlightJobTypePriorityEnum.SYSTEM_NOT_WORKER_JOB.getCode();
        }
        if (this.getDataSource().equals(FlightJobDataSourceEnum.USER.getVal()) && ObjUtil.isNull(
                this.getWorkerCode())) {
            this.flightJobType = FlightJobTypePriorityEnum.USER_NOT_WORKER_JOB.getCode();
        }
        if (!this.getDataSource().equals(FlightJobDataSourceEnum.SYSTEM.getVal())
                && ObjUtil.isNotEmpty(this.getWorkerCode())) {
            this.flightJobType = FlightJobTypePriorityEnum.USER_HAVE_WORKER_JOB.getCode();
        }
        if (this.flightJobType == null) {
            this.flightJobType = "other";
        }
    }

结语:如果有更好的实现方式,或者有疑问的地方,欢迎评论区讨论。