如何使用策略模式处理多种类型请求

栏目: 后端 · 发布时间: 6年前

内容简介:现在有一个活动,活动场景包含布置书籍作业,布置短文作业,布置一课一练作业(以后还可能会新增其它类型的活动),每一种活动场景有自己对应的完成逻辑和奖励。现在定义对应的场景值如下:解决方案一:因为布置作业和完成活动属于不同的项目,我采用的是消息队列的方式(消息队列不是本文讨论的重点),布置作业时发送消息,传递对应的活动场景值和其它必须的参数过来,消费端收到消息之后,根据对应的场景值作出相应的处理,伪代码如下:这种方式是最简单的,也是最容易理解的,但是存在的问题是,如果现在新增新的活动场景,原来的if else

现在有一个活动,活动场景包含布置书籍作业,布置短文作业,布置一课一练作业(以后还可能会新增其它类型的活动),每一种活动场景有自己对应的完成逻辑和奖励。现在定义对应的场景值如下:

活动名称 活动场景值
布置书籍作业 11
布置短文作业 12
布置一课一练作业 13

2.解决方案

解决方案一:因为布置作业和完成活动属于不同的项目,我采用的是消息队列的方式(消息队列不是本文讨论的重点),布置作业时发送消息,传递对应的活动场景值和其它必须的参数过来,消费端收到消息之后,根据对应的场景值作出相应的处理,伪代码如下:

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

这种方式是最简单的,也是最容易理解的,但是存在的问题是,如果现在新增新的活动场景,原来的if else后面要新增新的代码和判断逻辑,对原有的代码具有侵入性;再者如果类型非常多的话,if else也会有很多,代码看起来不够优雅。 解决方案二:采用策略模式来解决,定义一个策略接口,布置书籍作业,短文作业,一课一练的处理逻辑都实现策略接口,根据传入的不同场景值选择不同的处理类。这也是使用策略模式最难的地方:如何根据传入的参数,找到对应的处理类,答案是:可以采用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,对应上面的场景值,可以看到在处理的过程中是调用了service来处理的,其它的短文任务,一课一练任务跟这个基本一样的,只是不同的场景值,只在展示一个布置短文作业的:

/**
 * 完成短文活动任务
 * @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);
    }

}
复制代码

这是整个过程的全部代码,如果现在新增其它活动场景(比如布置假期作业),那么直接写一个布置假期作业的处理类,新增一个对应的场景值就可以了,对原有代码不侵入。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

轻量级Django

轻量级Django

茱莉亚·埃尔曼 (Julia Elman)、马克·拉温 (Mark Lavin) / 侯荣涛、吴磊 / 中国电力出版社; 第1版 / 2016-11-1 / 35.6

自Django 创建以来,各种各样的开源社区已经构建了很多Web 框架,比如JavaScript 社区创建的Angular.js 、Ember.js 和Backbone.js 之类面向前端的Web 框架,它们是现代Web 开发中的先驱。Django 从哪里入手来适应这些框架呢?我们如何将客户端MVC 框架整合成为当前的Django 基础架构? 本书讲述如何利用Django 强大的“自支持”功......一起来看看 《轻量级Django》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

随机密码生成器
随机密码生成器

多种字符组合密码

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具