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行),大量简化了代码量,并且很容易写测试代码,也增长了可读性和可维护性。

相关文章
相关标签/搜索