java開發中的鏈式思惟 —— 設計一個鏈式過濾器

     本文爲原創博文,轉載請註明出處,侵權必究!html

  • 概述  

  最近在弄阿里雲的sls日誌服務,該服務提供了一個搜索接口,可根據各類運算、邏輯等表達式搜出想要的內容。具體語法可見https://help.aliyun.com/document_detail/29060.html?spm=5176.doc29029.2.2.8PG8RAjava

  在開發中,咱們須要用到該接口的查詢需求會不斷擴增,可能一開始只用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行),大量簡化了代碼量,並且很容易寫測試代碼,也增長了可讀性和可維護性。

相關文章
相關標籤/搜索