Spring 環(huán)境下實現(xiàn)策略模式的示例
背景
最近在忙一個需求,大致就是給滿足特定條件的用戶發(fā)營銷郵件,但是用戶的來源有很多方式:從 ES 查詢的、從 csv 導(dǎo)入的、從 MongoDB 查詢….. 需求很簡單,但是怎么寫的優(yōu)雅,方便后續(xù)擴展,就存在很多門道了。
我們的項目是基于 Spring Boot 開發(fā)的,因此這篇文章也會基于 Spring Boot 作為基礎(chǔ)框架,教你如何使用 Spring 依賴注入的特性,優(yōu)雅的實現(xiàn)策略模式。
1. 簡單粗暴
最簡單粗暴直接的方式莫過于 if...else… 了,偽代碼如下:
if(來源 == ES){ // TODO: ES Query }else if(來源 == CSV){ // TODO: Read CSV File }else if(來源 == MongoDB){ // TODO: MongoDB Query }
如果后面還需要從其他平臺獲取,那就在接著添加 else if...,這種方式固然簡單直接,但是當(dāng)后續(xù)擴展的方式越來越多,相應(yīng)的if...else...也會越來越長,emmm….. 怎么說呢,黑貓白貓,能抓到老鼠的就是好貓。
2. 策略模式
在 Spring 環(huán)境下實現(xiàn)策略模式異常簡單,畢竟 Spring 提供的依賴注入簡直就是開發(fā)利器~
既然是策略模式,那么定義策略肯定是首當(dāng)其沖,策略我們使用枚舉實現(xiàn)最佳。
public enum GroupType { /** * 從 ES 查詢 */ ES, /** * 從 MongoDB 查詢 */ MONGODB, /** * 從 文件 讀取 */ FILE }
下一步,我們定義一個接口,用于抽象通用的功能。
public interface IGroupSelect { /** * 人群獲取方式 * * @return 人群選擇枚舉 */ GroupType type(); /** * 查詢滿足條件的用戶 * * @param groupQuery 查詢條件 * @return 滿足條件的用戶 */ default List<GroupUser> queryUser(GroupQuery groupQuery) { checkQueryCondition(groupQuery); return doQuery(groupQuery); } /** * 事前校驗查詢條件 * * @param groupQuery 查詢條件 * @throws IllegalArgumentException 參數(shù)異常 */ void checkQueryCondition(GroupQuery groupQuery) throws IllegalArgumentException; /** * 真正的查詢方法 * * @param groupQuery 查詢條件 * @return 滿足條件的用戶 */ List<GroupUser> doQuery(GroupQuery groupQuery); }
這一步,小伙伴們有沒有發(fā)現(xiàn)里面也包含了模板方法模式呢?
然后就是不同策略的具體實現(xiàn)了。
- ES 策略
@Slf4j @Service public class EsGroupSelect implements IGroupSelect { /** * 人群獲取方式 * * @return 人群選擇枚舉 */ @Override public GroupType type() { return GroupType.ES; } /** * 事前校驗查詢條件 * * @param groupQuery 查詢條件 * @throws IllegalArgumentException 參數(shù)異常 */ @Override public void checkQueryCondition(GroupQuery groupQuery) throws IllegalArgumentException { log.info("groupQuery = {}", groupQuery); } /** * 查詢滿足條件的用戶 * * @param groupQuery 查詢條件 * @return 滿足條件的用戶 */ @Override public List<GroupUser> doQuery(GroupQuery groupQuery) { List<GroupUser> result = new ArrayList<>(); // TODO: // 1. 復(fù)雜的 ES 查詢邏輯 // 2. 根據(jù)條件篩選滿足條件的用戶數(shù)據(jù) for (int i = 1; i <= 15; i++) { result.add(GroupUser.of("ES用戶" + i, i + "@es.com")); } return result; } }
- 文件策略
@Slf4j @Service public class FileGroupSelect implements IGroupSelect { /** * 人群獲取方式 * * @return 人群選擇枚舉 */ @Override public GroupType type() { return GroupType.FILE; } /** * 事前校驗查詢條件 * * @param groupQuery 查詢條件 * @throws IllegalArgumentException 參數(shù)異常 */ @Override public void checkQueryCondition(GroupQuery groupQuery) throws IllegalArgumentException { log.info("groupQuery = {}", groupQuery); } /** * 查詢滿足條件的用戶 * * @param groupQuery 查詢條件 * @return 滿足條件的用戶 */ @Override public List<GroupUser> doQuery(GroupQuery groupQuery) { List<GroupUser> result = new ArrayList<>(); // TODO: // 1. 復(fù)雜的解析、讀文件 // 2. 根據(jù)條件篩選滿足條件的用戶數(shù)據(jù) for (int i = 1; i <= 3; i++) { result.add(GroupUser.of("文件讀取用戶" + i, i + "@file.com")); } return result; } }
- MongoDB 策略
@Slf4j @Service public class MongoGroupSelect implements IGroupSelect { /** * 人群獲取方式 * * @return 人群選擇枚舉 */ @Override public GroupType type() { return GroupType.MONGODB; } /** * 事前校驗查詢條件 * * @param groupQuery 查詢條件 * @throws IllegalArgumentException 參數(shù)異常 */ @Override public void checkQueryCondition(GroupQuery groupQuery) throws IllegalArgumentException { log.info("groupQuery = {}", groupQuery); } /** * 查詢滿足條件的用戶 * * @param groupQuery 查詢條件 * @return 滿足條件的用戶 */ @Override public List<GroupUser> doQuery(GroupQuery groupQuery) { List<GroupUser> result = new ArrayList<>(); // TODO: // 1. 復(fù)雜的 MongoDB 查詢邏輯 // 2. 根據(jù)條件篩選滿足條件的用戶數(shù)據(jù) for (int i = 1; i <= 7; i++) { result.add(GroupUser.of("MongoDB用戶" + i, i + "@mongo.com")); } return result; } }
現(xiàn)在到了最后一步,就是如何通過 Spring 優(yōu)雅的實現(xiàn)策略模式的選擇呢?敲黑板,考試必考!
我們通過定義一個工廠類,然后使用 Spring 的依賴注入特性,可以注入一個接口的多個實現(xiàn),這里采用 List<IGroupSelect>
的形式注入,Spring 也支持通過 Map<String,IGroupSelect>
的形式注入,如果使用 Map 注入,那么 key 就是類名,小伙伴們自己也可以測試一下~
@Service public class GroupSelectFactory { @Autowired private List<IGroupSelect> groupSelectList; /** * 根據(jù)人群類型選擇具體的實現(xiàn)類 * * @param type 人群類型 * @return 人群選擇具體實現(xiàn)類 */ public IGroupSelect getGroupSelect(GroupType type) { Optional<IGroupSelect> groupSelectOptional = groupSelectList.stream().filter(t -> t.type() == type).findAny(); return groupSelectOptional.orElseThrow(() -> new IllegalArgumentException("暫不支持該人群方式")); } }
最后寫個定時任務(wù)測試一下吧。
@Autowired private GroupSelectFactory groupSelectFactory; /** * 模擬定時發(fā)送營銷郵件 */ @Scheduled(cron = "0/10 * * * * ?") public void sendEmailTask() { List<SendEmailTask> taskList = new ArrayList<>(); for (GroupType groupType : GroupType.values()) { GroupQuery groupQuery = new GroupQuery("虛頭巴腦的 " + groupType.name() + " 查詢條件"); taskList.add(SendEmailTask.of(groupType, groupQuery)); } taskList.forEach(task -> { List<GroupUser> groupUsers = groupSelectFactory.getGroupSelect(task.getType()).queryUser(task.getQuery()); log.info("groupUsers = {}", groupUsers); }); } @Data @NoArgsConstructor @AllArgsConstructor(staticName = "of") static class SendEmailTask implements Serializable { private static final long serialVersionUID = -3461263089669779193L; private GroupType type; private GroupQuery query; }
觀察控制臺,看看日志輸出吧~
總結(jié)
- 本文使用策略模式實現(xiàn)不同人群的查詢,后續(xù)如果要增加短信、微信、釘釘?shù)南l(fā)送,是不是也可以用策略模式實現(xiàn)呢?
- 使用 Spring 的依賴注入特性,可以注入一個接口的多個實現(xiàn),很容易就實現(xiàn)了策略模式的選擇,這樣后續(xù)添加一種策略的時候,完全不需要改動主要邏輯,只需添加具體實現(xiàn)即可。
- 細心的小伙伴可以發(fā)現(xiàn),本文雖然是講策略模式,其實里面還包含了模板方法、工廠模式,多種設(shè)計模式的協(xié)同作戰(zhàn),食用味道更佳喲~
配套代碼:https://github.com/xkcoding/practice_demo/tree/master/strategy-design-pattern-in-spring
以上就是Spring 環(huán)境下實現(xiàn)策略模式的示例的詳細內(nèi)容,更多關(guān)于Spring 實現(xiàn)策略模式的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解Java中的do...while循環(huán)語句的使用方法
這篇文章主要介紹了Java中的do...while循環(huán)語句的使用方法,是Java入門學(xué)習(xí)中的基礎(chǔ)知識,需要的朋友可以參考下2015-10-10SpringBoot使用@PathVariable進行數(shù)據(jù)校驗的流程步驟
在SpringBoot項目中,我們經(jīng)常需要從 URL 中獲取參數(shù)并進行相關(guān)的數(shù)據(jù)校驗,而@PathVariable注解就是一種非常方便的方式,可以讓我們在方法參數(shù)中直接獲取URL中的參數(shù),并進行數(shù)據(jù)校驗,本文將介紹如何使用@PathVariable注解進行數(shù)據(jù)校驗2023-06-06java8 stream sort自定義復(fù)雜排序案例
這篇文章主要介紹了java8 stream sort自定義復(fù)雜排序案例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-10-10Spring:@Async注解和AsyncResult與CompletableFuture使用問題
這篇文章主要介紹了Spring:@Async注解和AsyncResult與CompletableFuture使用問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08