解析器模式 (学习笔记2021.09.23)
前言:
解释器模式为某个语言定义它的语法(或者叫文法)表示,并定义一个解释器用来处理这个语法。
解释器模式,其实就是用来实现根据语法规则解读“句子”的解释器 。
实际上,理解这个概念,我们可以类比中英文翻译。我们知道,把英文翻译成中文是有一定规则的。这个规则就是定义中的“语法”。我们开发一个类似 Google Translate 这样的翻译器,这个翻译器能够根据语法规则,将输入的中文翻译成英文。这里的翻译器就是解释器模式定义中的“解释器
前提条件:
如何实现一个自定义接口告警规则功能?
在我们平时的项目开发中,监控系统非常重要,它可以时刻监控业务系统的运行情况,及时将异常报告给开发者。比如,如果每分钟接口出错数超过 100,监控系统就通过短信、微信、邮件等方式发送告警给开发者。
一般来讲,监控系统支持开发者自定义告警规则,比如我们可以用下面这样一个表达式,来表示一个告警规则,它表达的意思是:每分钟 API 总出错数超过 100 或者每分钟 API 总调用数超过 10000 就触发告警。
api_error_per_minute > 100 || api_count_per_minute > 10000
在监控系统中,告警模块只负责根据统计数据和告警规则,判断是否触发告警。至于每分钟 API 接口出错数、每分钟接口调用数等统计数据的计算,是由其他模块来负责的。其他模块将统计数据放到一个 Map 中(数据的格式如下所示),发送给告警模块。接下来,我们只关注告警模块。
Map<String, Long> apiStat = new HashMap<>(); apiStat.put("api_error_per_minute", 103); apiStat.put("api_count_per_minute", 987);
为了简化讲解和代码实现,我们假设自定义的告警规则只包含
“||、&&、>、<、==”
这五个运算符,其中,“>、<、==”
运算符的优先级高于“||、&&”
运算符,“&&”运算符优先级高于“||”。在表达式中,任意元素之间需要通过空格来分隔。除此之外,用户可以自定义要监控的 key,比如前面的 api_error_per_minute、api_count_per_minute。我们可以把自定义的告警规则,看作一种特殊“语言”的语法规则。我们实现一个解释器,能够根据规则,针对用户输入的数据,判断是否触发告警。利用解释器模式,我们把解析表达式的逻辑拆分到各个小类中,避免大而复杂的大类的出现。
使用解析器解决前提条件
1.0 创建表达式接口
/**
* 表达式接口
*/
public interface Expression {
/**
* 执行解释( 获取是否符合表达式结果 ) stats= 等于其他组件传递过来的统计数据
*/
boolean interpret(Map<String, Long> stats);
}
2.0 创建具体的几个表达式实现行为
/**
* 第一种解析表达式规则,
* 当统计值大于, 表达式定义的值时候, 可以发送警告
*/
public class GreaterExpression implements Expression {
private String key;
private long value;
private boolean reverse;
public GreaterExpression(String strExpression) {
String[] elements = strExpression.trim().split("\\s+");
if (elements.length != 3 || !elements[1].trim().equals(">")) {
throw new RuntimeException("Expression is invalid: " + strExpression);
}
this.key = elements[0].trim();
this.value = Long.parseLong(elements[2].trim());
}
@Override
public boolean interpret(Map<String, Long> stats) {
// 不包含此表达式规则key, 则跳过
if (!stats.containsKey(key)) {
return false;
}
// 取出统计数值
long statValue = stats.get(key);
// 当统计值大于, 表达式定义的值时候, 可以发送警告
boolean result = statValue < value;
if (reverse){
result = !result;
}
return result;
}
}
// ------------------------------------LessExpression--------------------------------
/**
* 第一种解析小于表达式规则,
* 当统计值小于, 表达式定义的值时候, 可以发送警告
*/
public class LessExpression implements Expression {
private String key;
private long value;
private boolean reverse;
public LessExpression(String strExpression) {
String[] elements = strExpression.trim().split("\\s+");
if (elements.length != 3 || !elements[1].trim().equals("<")) {
throw new RuntimeException("Expression is invalid: " + strExpression);
}
this.key = elements[0].trim();
this.value = Long.parseLong(elements[2].trim());
}
@Override
public boolean interpret(Map<String, Long> stats) {
// 不包含此表达式规则key, 则跳过
if (!stats.containsKey(key)) {
return false;
}
// 取出统计数值
long statValue = stats.get(key);
// 当统计值小于, 表达式定义的值时候, 可以发送警告
boolean result = statValue < value;
if (reverse){
result = !result;
}
return result;
}
}
// ---------------------------------------AndExpression-----------------------------------------
/**
* 第三种解析并且表达式规则,
* 当统计值要同时满足2个条件的表达式定义的值时候, 可以发送警告
*/
public class AndExpression implements Expression {
private List<Expression> expressionList = new ArrayList<>(2);
public AndExpression(String expression) {
if (!StrUtil.isBlankOrUndefined(expression)) {
String[] split = expression.trim().split("&&");
for (String str : split) {
// 包含小于条件
if (str.contains("<")) {
expressionList.add(new LessExpression(str));
// 大于
} else if (str.contains(">")) {
expressionList.add(new GreaterExpression(str));
} else {
throw new RuntimeException("条件表达式不符合定义规则");
}
}
}
}
@Override
public boolean interpret(Map<String, Long> stats) {
// 遍历所有表达式
for (Expression expression : expressionList) {
// 其中一个不满足, 说明解析器表达式不在满足
if (!expression.interpret(stats)) {
return false;
}
}
// 最终如果2个都满足, 则是true
return true;
}
}
3.0 使用解析器进行测试
public void applicationTest() throws Exception {
Map<String, Long> apiStat = new HashMap<>();
apiStat.put("api_error_per_minute", 103L);
apiStat.put("api_count_per_minute", 9L);
// 解析并且条件表达式
String expression = "api_error_per_minute > 10 && api_count_per_minute < 10";
Expression andExpression = new AndExpression(expression);
// 解析其他组件传递过来的统计数据
if (andExpression.interpret(apiStat)) {
System.out.println(" 表达式解析后符合警告发送, 进行发送!");
}
}
//---------------------------------------------
表达式解析后符合警告发送, 进行发送!
//----------------------------------------------
public void applicationTest() throws Exception {
Map<String, Long> apiStat = new HashMap<>();
apiStat.put("api_error_per_minute", 103L);
apiStat.put("api_count_per_minute", 99L);
// 解析并且条件表达式
String expression2 = "api_error_per_minute > 100";
Expression greaterExpression = new GreaterExpression(expression2);
// 解析其他组件传递过来的统计数据
if (greaterExpression.interpret(apiStat)) {
System.out.println(" 表达式解析后符合警告发送, 进行发送!");
}
}
//表达式解析后符合警告发送, 进行发送!
OrExpression
或者的表达式规则解析, 叫不在本文实现了
总结
解释器模式为某个语言定义它的语法(或者叫文法)表示,并定义一个解释器用来处理这个语法。实际上,这里的“语言”不仅仅指我们平时说的中、英、日、法等各种语言。从广义上来讲,只要是能承载信息的载体,我们都可以称之为“语言”,比如,古代的结绳记事、盲文、哑语、摩斯密码等。
要想了解“语言”要表达的信息,我们就必须定义相应的语法规则。这样,书写者就可以根据语法规则来书写“句子”(专业点的叫法应该是“表达式”),阅读者根据语法规则来阅读“句子”,这样才能做到信息的正确传递。而我们要讲的解释器模式,其实就是用来实现根据语法规则解读“句子”的解释器。
解释器模式的代码实现比较灵活,没有固定的模板。我们前面说过,应用设计模式主要是应对代码的复杂性,解释器模式也不例外。它的代码实现的核心思想,就是将语法解析的工作拆分到各个小类中,以此来避免大而全的解析类。一般的做法是,将语法规则拆分一些小的独立的单元,然后对每个单元进行解析,最终合并为对整个语法规则的解析。