Как обрабатывать несколько типов запросов с помощью шаблона стратегии

Java задняя часть Spring

1. Краткое описание требований

Теперь есть активность. Сцена активности включает в себя задание книжных заданий, назначение заданий для эссе, назначение одного урока и одного практического задания (другие типы действий могут быть добавлены в будущем). Каждая сцена действия имеет свою собственную соответствующую логику завершения и награды. Теперь определите соответствующие значения сцены следующим образом:

Название события Значение активной сцены
Назначать задания книги 11
Назначать задания для эссе 12
Назначить один урок и одно практическое домашнее задание 13

2. Решения

Решение а. Поскольку задания и действия по завершению относятся к разным элементам, я использую очереди режима сообщений (фокус очереди сообщений здесь не обсуждается), передает назначения сообщений, ретранслирует сцену, соответствующую значению, и другие необходимые параметры. После этого, потребитель получает сообщение о завершении, выполняет соответствующую обработку в соответствии со значением, соответствующим сцене, следующим псевдокодом:

if(场景值==11){
    //完成书籍作业的相关逻辑
}else if(场景值==12){
    //完成短文作业的相关逻辑
}else if(场景值==13){
    //完成一课一练作业的相关逻辑
}

Этот метод является самым простым и легким для понимания, но проблема в том, что если сейчас добавляется новая сцена активности, новый код и логика суждения должны быть добавлены после исходного if else, что не влияет на исходный код. Кроме того, если типов слишком много, будет много if elses, и код не будет выглядеть элегантно. Решение 2. Используйте режим стратегии для решения проблемы, определите интерфейс стратегии, упорядочите задания для книг, задания для эссе и логику обработки одного урока и одной практики — все реализуют интерфейс стратегии и выбирают разные классы обработки в соответствии с различными поступающими сообщениями. значения сцены. Это также самая сложная часть использования шаблона стратегии: как найти соответствующий класс обработки в соответствии с входящими параметрами, ответ: вы можете использовать Spring getBean или отражение Java. Я загружаю все классы политик в память при запуске программы, и выбираю соответствующий класс обработки по поступающим параметрам при обработке запроса.

3. Практичный код

3.1 Определение интерфейса политики

/**
 * 活动策略接口
 * @author junzhongliu
 * @date 2018/9/30 17:11
 */
public interface ActivityStrategyInterface {

    /**
     * 教师创建或更新活动记录
     * @param userId 用户id
     * @param scene 场景
     * @param condition 本活动完成的条件
     */
    void doActivityAction(Long userId,Integer scene,Integer condition);
}

3.2 Пользовательские аннотации

Чтобы легко сравнивать входящие значения сцены и выбирать соответствующий класс обработки стратегии, я настроил аннотацию

/**
 * 活动场景注解
 * @author junzhongliu
 * @date 2018/9/30 17:24
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivitySceneAnnotation {

    /**
     * 活动场景id,默认值1
     */
    int sceneId() default 1;
}

3.3 Определите соответствующий интерфейс обработки политики

Это класс реализации стратегии, который действительно имеет дело с организацией заданий для книг, заданий для эссе и одного практического задания для одного урока.Они должны реализовать интерфейс стратегии.Код выглядит следующим образом:

/**
 * 布置书籍任务
 * @author junzhongliu
 * @date 2018/9/30 17:13
 */
@Slf4j
@Service
@ActivitySceneAnnotation(sceneId = ActivitySceneConstants.BOOK_ACTIVITY)
public class BookStrategy implements ActivityStrategyInterface {

    @Autowired
    TeacherActivityRecordService teacherActivityRecordService;

    @Override
    public void doActivityAction(Long userId, Integer scene, Integer condition) {
        log.info("desc:{},userId:{},scene:{},condition:{}","布置书籍任务[STRATEGY]",userId,scene,condition);
        teacherActivityRecordService.saveOrUpdateRookieActivityRecord(userId,scene,condition);
    }
}

Здесь ActivitySceneAnnotation — наша кастомная аннотация выше ActivitySceneConstants.BOOK_ACTIVITY — кастомная константа Реальное значение 11, что соответствует указанному выше значению сцены Вы можете видеть, что служба вызывается во время обработки Другие короткие тексты Задачи, один урок , одно практическое задание в основном такое же, как это, но с другими значениями сцены, показывает только короткое задание эссе:


/**
 * 完成短文活动任务
 * @author junzhongliu
 * @date 2018/9/30 17:13
 */
@Slf4j
@Service
@ActivitySceneAnnotation(sceneId = ActivitySceneConstants.PASSAGE_ACTIVITY)
public class PassageStrategy implements ActivityStrategyInterface {

    @Resource
    TeacherActivityRecordService teacherActivityRecordService;

    @Override
    public void doActivityAction(Long userId, Integer scene, Integer condition) {
        log.info("desc:{},userId:{},scene:{},condition:{}","布置短文任务[STRATEGY]",userId,scene,condition);
        teacherActivityRecordService.saveOrUpdateRookieActivityRecord(userId,scene,condition);
    }
}

3.4 Выберите соответствующий класс обработки в соответствии со значением сцены

При запуске программы я использую аннотацию @PostConstruct для загрузки в память всех классов стратегии, реализующих интерфейс ActivityStrategyInterface.Пользователь запрашивает передать значение сцены.Согласно значению сцены выбирается соответствующий класс обработки.Все код выглядит следующим образом:

/**
 * 策略处理类的工厂类
 * @author junzhongliu
 * @date 2018/9/30 17:17
 */
@Service
public class ActivityStrategyFactory {

    private static final Map<String,ActivityStrategyInterface> STRATEGY_BEAN_CACHE = Maps.newConcurrentMap();
    @Autowired
    private ApplicationContext applicationContext;

    /**
     * 根据不同的场景创建不同的策略
     * 实现思路:遍历策略列表的所有策略,获取策略的注解,
     * 比对场景值是否一致,场景值一致则返回当前策略的实例对象
     * @param scene 场景值
     * @return
     */
    public ActivityStrategyInterface createStrategy(Integer scene) {

        Optional<ActivityStrategyInterface> strategyOptional =
                STRATEGY_BEAN_CACHE
                        .entrySet()
                        .stream()
                        .map(e -> {
            ActivitySceneAnnotation validScene = e.getValue().getClass().getDeclaredAnnotation(ActivitySceneAnnotation.class);
            if (Objects.equals(validScene.sceneId(),scene)) {
                    return e.getValue();
            }
            return null;
        }).filter(Objects::nonNull)
                        .findFirst();
        if(strategyOptional.isPresent()){
            return strategyOptional.get();
        }
        throw new RuntimeException("策略获得失败");
    }

    /**
     * 初始化策略列表
     */
    @PostConstruct
    private void init() {
        STRATEGY_BEAN_CACHE.putAll(applicationContext.getBeansOfType(ActivityStrategyInterface.class));
    }

}

До сих пор обработка, связанная с политикой, была определена, давайте посмотрим, как ее использовать.

3.5 Как использовать

Я вызвал ActivityStrategyFactory у потребителя сообщения, передал значение сцены и получил класс обработки Код выглядит следующим образом:

/**
 * 消费其它模块的消息,创建或更新教师活动记录
 * @author junzhongliu
 * @date 2018/9/30 16:50
 */
@Slf4j
@Service
public class CreateActivityRecordMessageConsumer implements MessageConsumer<CreateActivityRecordMessage> {

    @Autowired
    private ActivityStrategyFactory strategyFactory;

    @Override
    public CreateActivityRecordMessage newMessageInstance() {
        return new CreateActivityRecordMessage();
    }

    @Override
    public void consume(CreateActivityRecordMessage message) throws Exception {
        log.info("desc:{},param:{}","创建任务记录消费消息[CONSUMER]",JSONObject.toJSONString(message));
        Long userId = message.getUserId();
        Integer scene = message.getScene();
        Integer condition = message.getCondition();
        if(Objects.isNull(userId) || Objects.isNull(scene) || Objects.isNull(condition)){
            return;
        }
        //创建具体的执行策略,并执行活动行为
        ActivityStrategyInterface strategy = strategyFactory.createStrategy(message.getScene());
        strategy.doActivityAction(userId,scene,condition);
    }

}

Это весь код всего процесса.Если вы добавите другие сценарии деятельности (такие как организация отпускных заданий), вы можете напрямую написать класс обработки для назначения отпускных заданий и добавить соответствующее значение сцены, не вторгаясь в исходный код.