1. Requirement description

Now there is an activity, the activity scene contains the assignment of books, assignment of short essays, assignment of lesson and practice (other types of activities may be added later), each activity scene has its own logic and reward for completion. Now define the corresponding scenario values as follows:

The name of the event Active scenario value
Assign books 11
Assign short assignments 12
Assign homework for each lesson 13

2. Solutions

Solution: for homework and finish activities belong to different projects, I adopt the way of the message queue (message queue is not the focus of this article, homework to send messages, transfer the activities of the corresponding scene value and other parameters which must come, consumers receive news value to make corresponding processing according to the corresponding scene, pseudo code is as follows:

if(Scenario value ==11){// Complete the logic related to the book assignment}else if(Scenario value ==12){// Complete the logic related to the short assignment}else if(Scenario value ==13){// Complete the relevant logic of the lesson and practice homework}Copy the code

This approach is the simplest and easiest to understand, but the problem is that if you add a new activity scenario, you have to add new code and judgment logic after the original if else, which is intrusive to the original code. Again, if you have a lot of types, and you have a lot of if else’s, it doesn’t look very elegant. Solution 2: adopt the strategy mode to solve the problem, define a policy interface, assign books, short homework, lesson and practice of the processing logic to implement the policy interface, according to the incoming different scenario values to choose different processing classes. This is the hardest part of using the policy pattern: how to find the corresponding processing class based on the parameters passed in. The answer is either Spring’s getBean or Java’s reflection. I load all the policy classes into memory when the program starts, and when the request is processed, I select the corresponding processing class based on the parameters passed in.

3. Practical code

3.1 Defining a Policy Interface

/ active policy interface * * * * @ author junzhongliu * @ date 2018/9/30 17:11 * / public interface ActivityStrategyInterface {/ * * * * @param userId @param Scene * @param condition Condition for this activity to complete */ voiddoActivityAction(Long userId,Integer scene,Integer condition);
}
Copy the code

3.2 Custom Annotations

To make it easy to compare the incoming scenario values and select the corresponding policy processing class, I have created a custom annotation

/** * @author junzhongliu * @date 2018/9/30 17:24 */ @target (elementType.type) @Retention(RetentionPolicy.runtime) public @Interface ActivitySceneAnnotation {/** * Default value 1 */ int sceneId() default 1; }Copy the code

3.3 Define the corresponding policy processing interface

Is really dealing with the assignment of books, short homework, a lesson a practice homework strategy implementation class, they are to achieve the policy interface, the code is as follows:

/** * Assign book task * @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 voiddoActivityAction(Long userId, Integer scene, Integer condition) {
        log.info("desc:{},userId:{},scene:{},condition:{}"."Assign book tasks [STRATEGY]",userId,scene,condition); teacherActivityRecordService.saveOrUpdateRookieActivityRecord(userId,scene,condition); }}Copy the code

ActivitySceneAnnotation here is above our custom annotations, ActivitySceneConstants. BOOK_ACTIVITY is custom constants, true value is 11, value corresponding to the above scenario, As you can see, the service is called to handle the process. The other short task, lesson by lesson task is basically the same, but with different scenario values, only to show a short task assigned:

/** * Complete the short text activity * @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 voiddoActivityAction(Long userId, Integer scene, Integer condition) {
        log.info("desc:{},userId:{},scene:{},condition:{}"."Assign a short task [STRATEGY]",userId,scene,condition); teacherActivityRecordService.saveOrUpdateRookieActivityRecord(userId,scene,condition); }}Copy the code

3.4 Select a processing class based on the scenario value

Here is the application starts, I used the @ PostConstruct annotations, will realize all ActivityStrategyInterface interface strategy classes are loaded into memory, the user requests to get a scene value, value according to the scene, select the corresponding processing class, all the code is as follows:

* @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; /** * Create different policies according to different scenarios * Implementation idea: iterate over all policies in the policy list to obtain policy annotations. * Check whether the scenario values are consistent. If the scenario values are consistent, return the instance object of the current policy * @param scene Scene value * @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("Strategy fails."); } /** * Initialize policy list */ @postConstruct private voidinit() { STRATEGY_BEAN_CACHE.putAll(applicationContext.getBeansOfType(ActivityStrategyInterface.class)); }}Copy the code

So far, the policy-related processing has been defined. How to use it

3.5 How to Use

I call ActivityStrategyFactory at the consumption of the message, pass in the scenario value, and get the handler class, as follows:

/** * consume messages from other modules, * @author junzhongliu * @date 2018/9/30 16:50 */ @slf4@service public class * @author junzhongliu * @date 2018/9/30 16:50 */ @slf4@service public class CreateActivityRecordMessageConsumer implements MessageConsumer<CreateActivityRecordMessage> { @Autowired private ActivityStrategyFactory strategyFactory; @Override public CreateActivityRecordMessagenewMessageInstance() {
        return new CreateActivityRecordMessage();
    }

    @Override
    public void consume(CreateActivityRecordMessage message) throws Exception {
        log.info("desc:{},param:{}"."Create tasks to record consumption messages [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; } / / create a specific execution strategy, and perform the activity ActivityStrategyInterface strategy. = strategyFactory createStrategy (message) getScene ()); strategy.doActivityAction(userId,scene,condition); }}Copy the code

This is the whole process of all the code, if now add other activities (such as homework holiday), then directly write a homework holiday processing class, add a corresponding scene value can, the original code does not invade.