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


     本文為原創博文,轉載請注明出處,侵權必究!

  • 概述  

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

  在開發中,我們需要用到該接口的查詢需求會不斷擴增,可能一開始只用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()就可以拿到他的表達式。下面只需要設計一個拼接器把多個單獨的查詢拼接在一起就好了。

  • 設計過濾器拼接器

  那么怎樣去設計呢?首先我想到了他的使用場景,對於單個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不斷的匹配重組。

  於是,我想到了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))

  雖然多了幾個括號,但表達式本身與我們所需要的邏輯是相同的含義。我把整個查詢語句的拼裝過程壓縮在了一行代碼里(上述第6、8行),大量簡化了代碼量,而且很容易寫測試代碼,也增加了可讀性和可維護性。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM