本文为原创博文,转载请注明出处,侵权必究!

  • 概述  

  最近在弄阿里云的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自动拼接成我们想要的逻辑表达式。

  • 设计单个过滤器

  需要首先想清楚的是,整体的表达式是由一个个单独的查询语句(运算表达式)组成的,而连接他们的是逻辑运算(与或非)。所以我的思路是,先将所有单独的运算表达式创建出来,最后通过逻辑运算将他们拼接在一起。

  下面是运算表达式过滤器的实现代码:

  

 public class AliyunLogFilter {

     public AliyunLogFilter() {}

     public AliyunLogFilter(MatchType type, String param, int value) {  //实现比较运算的表达式:type为运算符、param为查询字段、value为该字段对应的值

         singleQuery = param + " " + type.getSymbol() + " " + value;
} public AliyunLogFilter(boolean isFuzzy, String param) {        //实现模糊查询的表达式:当isFuzzy为true,表示模糊查询。 singleQuery = param + (isFuzzy ? "*" : "");
} public AliyunLogFilter(String key, String value) {           //实现键值对查询的表达式 singleQuery = key + ":" + value;
} /** 属性比较类型. */
public enum MatchType {                           //属性的比较类型,这里通过让enum维护一个字段symbol,可以在调用时根据MatchType的类型,直接获取对应的符号字符串。类似于多态。 EQ("="), LT(">"), ST("<"), LE(">="), SE("<="); private String symbol; private MatchType(String symbol) { this.symbol = symbol;
} public String getSymbol() { return symbol;
}
} private String singleQuery;                         //运算过滤器维护的唯一属性,即单个查询语句字符串。 public String get() {                             //通过get方法可获取这个过滤器下的查询语句。 return this.singleQuery;
}
}

  单个的查询做好了,通过构造函数我们可以直接生成对应的filter,调用get()就可以拿到他的表达式。下面只需要设计一个拼接器把多个单独的查询拼接在一起就好了。

  • 设计过滤器拼接器

  那么怎样去设计呢?首先我想到了他的使用场景,对于单个filter,使用很简单,每次都new Filter(param...)就可以了。但作为一个拼接工具,他的核心价值是把多个filter拼接起来的动作,而不是拼接类本身。按照传统的方式,可能我们会这样:在Assembler内部维护一个List<Filter>,然后维护一个List<LogicSymbol>(或者可能直接搞一个HashMap<Filter, LogicSymbol>)。然后先创建Assembler实例,把filter依次添加,像这样:

     Assembler assembler = new Assembler();
assembler.getFilters().add(filter1);
assembler.getFilters().add(logicalSymbol1);
assembler.getFilters().add(filter2);
assembler.getFilters().add(logicalSymbol2);
assembler.getFilters().add(filter3);
assembler.getFilters().add(logicalSymbol3);
String queryStr = assembler.generateTotalQuery();

  这样写一个明显的缺陷就是,代码非常的臃肿古板,而且很难明显的看出各个filter之间的关联,我甚至觉得generateTotalQuery里面的实现会更加复杂,因为他要对两个list不断的匹配重组。

  于是,我想到了java 8里非常好用的stream,对于遍历操作,stream流的链式写法带给我们极大的代码简洁度和可读性。在这里,我可以同样用链式写法用一句话生成最终拼接好的查询语句。

  下面是过滤器拼接器的实现代码:

 public class AliyunLogFilterAssembler {

     private String queryStr;                                   //最终多个filter拼接好的完整查询表达式。

     public AliyunLogFilterAssembler() {}

     public AliyunLogFilterAssembler(String queryStr) { this.queryStr = queryStr; }   //重写构造函数,可初始化查询表达式。

     public String get() {                                                //类似于上面的单个过滤器,这里也通过get()直接拿到拼接器拼接好的表达式。

         return this.queryStr;
} //如果first为非操作,这里可能无法表达,暂时可直接用String参数直接传入"not param";
public static AliyunLogFilterAssembler create(AliyunLogFilter first) {        //类似一个静态的工厂方法,创建一个Assembler实例,并传入了这个拼接器的第一个过滤条件first。 return create(first.get());
} public static AliyunLogFilterAssembler create(String firstQueryStr) {         //同上,这里通过调用带参数的构造函数,初始化了拼接器的变量queryStr。 return new AliyunLogFilterAssembler(firstQueryStr);
} public AliyunLogFilterAssembler and(AliyunLogFilter filter) {              //定义了"与"操作,可传入一个过滤器,与当前的拼接器逻辑表达式形成与的关系。 return and(filter.get());
} public AliyunLogFilterAssembler and(String queryString) {             //"与"操作的具体实现,遵循阿里云提供的逻辑表达式规范,将filter的表达式拼接到拼接器中。 this.queryStr += " and (" + queryString + ")";
return this;
} public AliyunLogFilterAssembler or(AliyunLogFilter filter) {               //同上类似,这里是"或"操作。 return or(filter.get());
} public AliyunLogFilterAssembler or(String queryString) {                 //同上。 this.queryStr += " or (" + queryString + ")";
return this;
} public AliyunLogFilterAssembler not(AliyunLogFilter filter) {               //同上 return not(filter.get());
} public AliyunLogFilterAssembler not(String queryString) {                 //同上  this.queryStr += " not (" + queryString + ")";
return this;
}
}

  具体的代码含义相信看了注释可以理解。我把每个逻辑函数都返回了当前的assembler,这样确保了链式写法的方式,也让assembler中的queryStr可以持续更新直到我输入所有过滤条件。

  • 验证效果

  至此,这个小轮子就算OK了,下面我们举几个例子来测试一下效果,对于单独的过滤器为了简洁,统一使用非模糊的字符串查询。先定义几个表达式:(1) a and b or c; (2) a and b or (c not d)

  测试代码如下:

         String query;
AliyunLogFilter a= new AliyunLogFilter(false, "a");
AliyunLogFilter b= new AliyunLogFilter(false, "b");
AliyunLogFilter c= new AliyunLogFilter(false, "c");
AliyunLogFilter d= new AliyunLogFilter(false, "d");
query = AliyunLogFilterAssembler.create(a).and(b).or(c).get(); //(1)
System.out.println(query);
query = AliyunLogFilterAssembler.create(a).and(b).or(AliyunLogFilterAssembler.create(c).not(d).get()).get(); //(2)
System.out.println(query);

  运行结果如下:

a and (b) or (c)
a and (b) or (c not (d))

  虽然多了几个括号,但表达式本身与我们所需要的逻辑是相同的含义。我把整个查询语句的拼装过程压缩在了一行代码里(上述第6、8行),大量简化了代码量,而且很容易写测试代码,也增加了可读性和可维护性。

java开发中的链式思维 —— 设计一个链式过滤器的更多相关文章

  1. JAVA开发中如何优化类的设计

    具体类依赖于抽象类,而非抽象类依赖于具体类.这样做有利于一个抽象类扩展多个具体类. 开放封闭原则:对扩展开放,对修改封闭. 1.永远保持数据私有 保持数据的私有是设计类时,必须重点考虑的问题.保持私有 ...

  2. 轻松理解 Java开发中的依赖注入(DI)和控制反转(IOC)

    前言 关于这个话题, 网上有很多文章,这里, 我希望通过最简单的话语与大家分享. 依赖注入和控制反转两个概念让很多初学这迷惑, 觉得玄之又玄,高深莫测. 这里想先说明两点: 依赖注入和控制反转不是高级 ...

  3. 编写高质量代码:改善Java程序的151个建议(第一章:JAVA开发中通用的方法和准则)

    编写高质量代码:改善Java程序的151个建议(第一章:JAVA开发中通用的方法和准则) 目录 建议1: 不要在常量和变量中出现易混淆的字母 建议2: 莫让常量蜕变成变量 建议3: 三元操作符的类型务 ...

  4. [ 转载 ] Java开发中的23种设计模式详解(转)

    Java开发中的23种设计模式详解(转)   设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类 ...

  5. Java开发中常用jar包整理及使用

    本文整理了我自己在Java开发中常用的jar包以及常用的API记录. <!-- https://mvnrepository.com/artifact/org.apache.commons/com ...

  6. Java开发中常见的危险信号(中)

    本文来源于我在InfoQ中文站原创的文章,原文地址是:http://www.infoq.com/cn/news/2013/12/common-red-flags-in-java-1 Dustin Ma ...

  7. Java开发中常见的危险信号(上)

    本文来源于我在InfoQ中文站原创的文章,原文地址是:http://www.infoq.com/cn/news/2013/12/common-red-flags-in-java-1 Dustin Ma ...

  8. Java开发中RMI和webservice区别和应用领域

    Java开发中RMI和webservice区别和应用领域 一.RMI和webservice区别和联系 0. 首先,都是远程调用技术. 1. RMI是在TCP协议上传递可序列化的java对象(使用Str ...

  9. paip.java 开发中web server的选择jboss resin tomcat比较..

    paip.java 开发中web server的选择jboss resin tomcat比较.. 作者Attilax  艾龙, EMAIL:1466519819@qq.com 来源:attilax的专 ...

随机推荐

  1. 福利:Axure 8.0 Pro 破解版下载

    今天从网上找了好久Axure 8.0 Pro版本 但是都不能用了,于是自己想到了这个办法 1.从官网下单 Axure 8.0 版本 官网地址:https://www.axure.com.cn/3510 ...

  2. EM and GMM(Theory)

    Part 1: Theory 目录: What's GMM? How to solve GMM? What's EM? Explanation of the result What's GMM? GM ...

  3. HTML5发展史

    2007年W3C(万维网联盟)立项HTML5,直至2014年10月底,这个长达八年的规范终于正式封稿. 在互联网的早期,对用户而言,能打开浏览器接入到互联网世界就是一个神奇的事情,但互联网发展到200 ...

  4. 如何一秒钟从头构建一个 ASP.NET Core 中间件

    前言 其实地上本没有路,走的人多了,也便成了路. -- 鲁迅 就像上面鲁迅说的那样,其实在我们开发中间件的过程中,微软并没有制定一些策略或者文档来约束你如何编写一个中间件程序, 但是其中却存在者一些最 ...

  5. weex官方demo weex-hackernews代码解读(下)

    weex 是阿里出品的一个类似RN的框架,可以使用前端技术来开发移动应用,实现一份代码支持H5,IOS和Android.而weex-hacknews则是weex官方出品的,首个使用 Weex 和 Vu ...

  6. BZOJ 3653: 谈笑风生(DFS序+可持久化线段树)

    首先嘛,还是太弱了,想了好久QAQ 然后,这道题么,明显就是求sigma(size[x]) (x是y的儿子且层树小于k) 然后就可以发现:把前n个节点按深度建可持久化线段树,就能用前缀和维护了 其实不 ...

  7. git的安装和环境配置过程(学习笔记)

    1.安装git 官网下载:https://github.com(目前官网好像找不到了,但是妙味的视频里面是在官网下载的)https://git-for-windows.github.io/ (廖雪峰老 ...

  8. eclipse+HBASE开发环境搭建(已实践)

    开发准备: jdk1.8.45 hbase-1.2.2(windows下和linux个留一份) hadoop-2.7.2(linux一份) Linux系统(centos或其它) Hadoop安装环境 ...

  9. linux oracle 10g tar.gz :xhost: unable to open display

    关于这个问题,最总要的一点是要理解xhost的作用,是干什么的,在下面的介绍中可以基本了解到,只要这个问题解决了,oracle就可以顺利安装了(这是建立在我还没碰到其它问题的基础上). 1. 以roo ...

  10. HttpHeplp 公共类 HttpWebRequest

    public class HttpHelp { public CookieContainer CookieContainer { get; set; } public CookieCollection ...