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);
}
}
複製程式碼
這是整個過程的全部程式碼,如果現在新增其它活動場景(比如佈置假期作業),那麼直接寫一個佈置假期作業的處理類,新增一個對應的場景值就可以了,對原有程式碼不侵入。