1.需求简述
现在有一个活动,活动场景包含布置书籍作业,布置短文作业,布置一课一练作业(以后还可能会新增其它类型的活动),每一种活动场景有自己对应的完成逻辑和奖励。现在定义对应的场景值如下:
活动名称 | 活动场景值 |
---|---|
布置书籍作业 | 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);
}
}
复制代码
这是整个过程的全部代码,如果现在新增其它活动场景(比如布置假期作业),那么直接写一个布置假期作业的处理类,新增一个对应的场景值就可以了,对原有代码不侵入。