分享

java开发中的链式思维 —— 设计一个链式过滤器

 yespon 2017-04-01
  • 概述  

  最近在弄阿里云的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,然后维护一个List(或者可能直接搞一个HashMap)。然后先创建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行),大量简化了代码量,而且很容易写测试代码,也增加了可读性和可维护性。

 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多