最近在弄阿里云的 sls 日志服务,该服务提供了一个搜索接口,可根据各种运算、逻辑等表达式搜出想要的内容。具体语法可见。
在开发中,我们需要用到该接口的查询需求会不断扩增,可能一开始只用 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 行),大量简化了代码量,而且很容易写测试代码,也增加了可读性和可维护性。
来源: http://www.cnblogs.com/jzb-blog/p/6653134.html