如何使用策略模式處理多種型別請求
現在有一個活動,活動場景包含佈置書籍作業,佈置短文作業,佈置一課一練作業(以後還可能會新增其它型別的活動),每一種活動場景有自己對應的完成邏輯和獎勵。現在定義對應的場景值如下:
活動名稱 | 活動場景值 |
---|---|
佈置書籍作業 | 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); } } 複製程式碼
這是整個過程的全部程式碼,如果現在新增其它活動場景(比如佈置假期作業),那麼直接寫一個佈置假期作業的處理類,新增一個對應的場景值就可以了,對原有程式碼不侵入。