本文爲原創博文,轉載請註明出處,侵權必究!html
最近在弄阿里雲的sls日誌服務,該服務提供了一個搜索接口,可根據各類運算、邏輯等表達式搜出想要的內容。具體語法可見https://help.aliyun.com/document_detail/29060.html?spm=5176.doc29029.2.2.8PG8RA。java
在開發中,咱們須要用到該接口的查詢需求會不斷擴增,可能一開始只用and,後面加了or和not,再後來又多了key/value pair、數值比較等等。若是把這些處理邏輯放在業務邏輯中,未免太過暴力,並且很是不方便維護和閱讀。尤爲是當出現了複雜的複合邏輯時,好比:"a and b or (c and (d not e))",咱們要先本身推算出具體的公式並顯示的寫在業務邏輯,顯然這是很不合理的。因此,我要把這類處理邏輯單獨抽離出來,查詢條件當成一個個搜索的過濾條件Filter,再經過拼接類Assembler自動拼接成咱們想要的邏輯表達式。函數
須要首先想清楚的是,總體的表達式是由一個個單獨的查詢語句(運算表達式)組成的,而鏈接他們的是邏輯運算(與或非)。因此個人思路是,先將全部單獨的運算表達式建立出來,最後經過邏輯運算將他們拼接在一塊兒。工具
下面是運算表達式過濾器的實現代碼:測試
1 public class AliyunLogFilter { 2 3 public AliyunLogFilter() {} 4 5 public AliyunLogFilter(MatchType type, String param, int value) { //實現比較運算的表達式:type爲運算符、param爲查詢字段、value爲該字段對應的值 6 7 singleQuery = param + " " + type.getSymbol() + " " + value; 8 } 9 10 public AliyunLogFilter(boolean isFuzzy, String param) { //實現模糊查詢的表達式:當isFuzzy爲true,表示模糊查詢。 11 12 singleQuery = param + (isFuzzy ? "*" : ""); 13 } 14 15 public AliyunLogFilter(String key, String value) { //實現鍵值對查詢的表達式 16 17 singleQuery = key + ":" + value; 18 } 19 20 /** 屬性比較類型. */ 21 public enum MatchType { //屬性的比較類型,這裏經過讓enum維護一個字段symbol,能夠在調用時根據MatchType的類型,直接獲取對應的符號字符串。相似於多態。 22 23 EQ("="), LT(">"), ST("<"), LE(">="), SE("<="); 24 25 private String symbol; 26 27 private MatchType(String symbol) { 28 29 this.symbol = symbol; 30 } 31 32 public String getSymbol() { 33 34 return symbol; 35 } 36 } 37 38 private String singleQuery; //運算過濾器維護的惟一屬性,即單個查詢語句字符串。 39 40 public String get() { //經過get方法可獲取這個過濾器下的查詢語句。 41 42 return this.singleQuery; 43 } 44 }
單個的查詢作好了,經過構造函數咱們能夠直接生成對應的filter,調用get()就能夠拿到他的表達式。下面只須要設計一個拼接器把多個單獨的查詢拼接在一塊兒就行了。this
那麼怎樣去設計呢?首先我想到了他的使用場景,對於單個filter,使用很簡單,每次都new Filter(param...)就能夠了。但做爲一個拼接工具,他的核心價值是把多個filter拼接起來的動做,而不是拼接類自己。按照傳統的方式,可能咱們會這樣:在Assembler內部維護一個List<Filter>,而後維護一個List<LogicSymbol>(或者可能直接搞一個HashMap<Filter, LogicSymbol>)。而後先建立Assembler實例,把filter依次添加,像這樣:阿里雲
1 Assembler assembler = new Assembler(); 2 assembler.getFilters().add(filter1); 3 assembler.getFilters().add(logicalSymbol1); 4 assembler.getFilters().add(filter2); 5 assembler.getFilters().add(logicalSymbol2); 6 assembler.getFilters().add(filter3); 7 assembler.getFilters().add(logicalSymbol3); 8 String queryStr = assembler.generateTotalQuery();
這樣寫一個明顯的缺陷就是,代碼很是的臃腫古板,並且很難明顯的看出各個filter之間的關聯,我甚至以爲generateTotalQuery裏面的實現會更加複雜,由於他要對兩個list不斷的匹配重組。spa
因而,我想到了java 8裏很是好用的stream,對於遍歷操做,stream流的鏈式寫法帶給咱們極大的代碼簡潔度和可讀性。在這裏,我能夠一樣用鏈式寫法用一句話生成最終拼接好的查詢語句。設計
下面是過濾器拼接器的實現代碼:日誌
1 public class AliyunLogFilterAssembler { 2 3 private String queryStr; //最終多個filter拼接好的完整查詢表達式。 4 5 public AliyunLogFilterAssembler() {} 6 7 public AliyunLogFilterAssembler(String queryStr) { this.queryStr = queryStr; } //重寫構造函數,可初始化查詢表達式。 8 9 public String get() { //相似於上面的單個過濾器,這裏也經過get()直接拿到拼接器拼接好的表達式。 10 11 return this.queryStr; 12 } 13 14 //若是first爲非操做,這裏可能沒法表達,暫時可直接用String參數直接傳入"not param"; 15 public static AliyunLogFilterAssembler create(AliyunLogFilter first) { //相似一個靜態的工廠方法,建立一個Assembler實例,並傳入了這個拼接器的第一個過濾條件first。 16 17 return create(first.get()); 18 } 19 20 public static AliyunLogFilterAssembler create(String firstQueryStr) { //同上,這裏經過調用帶參數的構造函數,初始化了拼接器的變量queryStr。 21 22 return new AliyunLogFilterAssembler(firstQueryStr); 23 } 24 25 public AliyunLogFilterAssembler and(AliyunLogFilter filter) { //定義了"與"操做,可傳入一個過濾器,與當前的拼接器邏輯表達式造成與的關係。 26 27 return and(filter.get()); 28 } 29 30 public AliyunLogFilterAssembler and(String queryString) { //"與"操做的具體實現,遵循阿里雲提供的邏輯表達式規範,將filter的表達式拼接到拼接器中。 31 32 this.queryStr += " and (" + queryString + ")"; 33 return this; 34 } 35 36 public AliyunLogFilterAssembler or(AliyunLogFilter filter) { //同上相似,這裏是"或"操做。 37 38 return or(filter.get()); 39 } 40 41 public AliyunLogFilterAssembler or(String queryString) { //同上。 42 43 this.queryStr += " or (" + queryString + ")"; 44 return this; 45 } 46 47 public AliyunLogFilterAssembler not(AliyunLogFilter filter) { //同上 48 49 return not(filter.get()); 50 } 51 52 public AliyunLogFilterAssembler not(String queryString) { //同上 53 54 this.queryStr += " not (" + queryString + ")"; 55 return this; 56 } 57 }
具體的代碼含義相信看了註釋能夠理解。我把每一個邏輯函數都返回了當前的assembler,這樣確保了鏈式寫法的方式,也讓assembler中的queryStr能夠持續更新直到我輸入全部過濾條件。
至此,這個小輪子就算OK了,下面咱們舉幾個例子來測試一下效果,對於單獨的過濾器爲了簡潔,統一使用非模糊的字符串查詢。先定義幾個表達式:(1) a and b or c; (2) a and b or (c not d)
測試代碼以下:
1 String query; 2 AliyunLogFilter a= new AliyunLogFilter(false, "a"); 3 AliyunLogFilter b= new AliyunLogFilter(false, "b"); 4 AliyunLogFilter c= new AliyunLogFilter(false, "c"); 5 AliyunLogFilter d= new AliyunLogFilter(false, "d"); 6 query = AliyunLogFilterAssembler.create(a).and(b).or(c).get(); //(1) 7 System.out.println(query); 8 query = AliyunLogFilterAssembler.create(a).and(b).or(AliyunLogFilterAssembler.create(c).not(d).get()).get(); //(2) 9 System.out.println(query);
運行結果以下:
a and (b) or (c)
a and (b) or (c not (d))
雖然多了幾個括號,但表達式自己與咱們所須要的邏輯是相同的含義。我把整個查詢語句的拼裝過程壓縮在了一行代碼裏(上述第六、8行),大量簡化了代碼量,並且很容易寫測試代碼,也增長了可讀性和可維護性。