设计模式之禅之设计模式-解析器模式

简介: 一:解析器模式定义        --->解释器模式(Interpreter Pattern)是一种按照规定语法进行解析的方案,在现在项目中使用较少        --->给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子二:解析器模式角色● AbstractExpression——抽象解释器        具体的解释任务由各个实现类完成,具体的解释器分别由TerminalExpression和Non-terminalExpression完成。

一:解析器模式定义
        --->解释器模式(Interpreter Pattern)是一种按照规定语法进行解析的方案,在现在项目中使用较少
        --->给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子


二:解析器模式角色




● AbstractExpression——抽象解释器
        具体的解释任务由各个实现类完成,具体的解释器分别由TerminalExpression和Non-terminalExpression完成。
● TerminalExpression——终结符表达式
        实现与文法中的元素相关联的解释操作,通常一个解释器模式中只有一个终结符表达式,但有多个实例,对应不同的终结符。具体到我们例子就是VarExpression类,表达式中的每个终结符都在栈中产生了一个VarExpression对象。
● NonterminalExpression——非终结符表达式
        文法中的每条规则对应于一个非终结表达式,具体到我们的例子就是加减法规则分别对应到AddExpression和SubExpression两个类。非终结符表达式根据逻辑的复杂程度而增加,原则上每个文法规则都对应一个非终结符表达式。
● Context——环境角色
        具体到我们的例子中是采用HashMap代替。


三:解析器模式的应用

【1】解析器模式的优点
        --->解释器是一个简单语法分析工具,它最显著的优点就是扩展性,修改语法规则只要修改相应的非终结符表达式就可以了,若扩展语法,则只要增加非终结符类就可以了。

【2】解析器模式的缺点

● 解释器模式会引起类膨胀
        -->每个语法都要产生一个非终结符表达式,语法规则比较复杂时,就可能产生大量的类文件,为维护带来了非常多的麻烦。
● 解释器模式采用递归调用方法
        -->每个非终结符表达式只关心与自己有关的表达式,每个表达式需要知道最终的结果,必须一层一层地剥茧,无论是面向过程的语言还是面向对象的语言,递归都是在必要条件下使用的,它导致调试非常复杂。想想看,如果要排查一个语法错误,我们是不是要一个断点一个断点地调试下去,直到最小的语法单元。
● 效率问题
        -->解释器模式由于使用了大量的循环和递归,效率是一个不容忽视的问题,特别是一用于解析复杂、冗长的语法时,效率是难以忍受的。


【3】解析器模式的使用场景
● 重复发生的问题可以使用解释器模式
        例如,多个应用服务器,每天产生大量的日志,需要对日志文件进行分析处理,由于各个服务器的日志格式不同,但是数据要素是相同的,按照解释器的说法就是终结符表达式都是相同的,但是非终结符表达式就需要制定了。在这种情况下,可以通过程序来一劳永逸地解决该问题。
● 一个简单语法需要解释的场景
        为什么是简单?看看非终结表达式,文法规则越多,复杂度越高,而且类间还要进行递归调用(看看我们例子中的栈)。想想看,多个类之间的调用你需要什么样的耐心和信心去排查问题。因此,解释器模式一般用来解析比较标准的字符集,例如SQL语法分析,不过该部分逐渐被专用工具所取代。
        在某些特用的商业环境下也会采用解释器模式,我们刚刚的例子就是一个商业环境,而且现在模型运算的例子非常多,目前很多商业机构已经能够提供出大量的数据进行分析。

【4】解析器模式的注意事项
        尽量不要在重要的模块中使用解释器模式,否则维护会是一个很大的问题。在项目中可以使用shell、JRuby、Groovy等脚本语言来代替解释器模式,弥补Java编译型语言的不足。我们在一个银行的分析型项目中就采用JRuby进行运算处理,避免使用解释器模式的四则运算,效率和性能各方面表现良好。

四:解析器模式的最佳实践
        解释器模式在实际的系统开发中使用得非常少,因为它会引起效率、性能以及维护等问题,一般在大中型的框架型项目能够找到它的身影,如一些数据分析工具、报表设计工具、科学计算工具等,若你确实遇到“一种特定类型的问题发生的频率足够高”的情况,准备使用解释器模式时,可以考虑一下Expression4J、MESP(Math Expression String Parser)、Jep等开源的解析工具包(这三个开源产品都可以通过百度、Google搜索到,请读者自行查询),功能都异常强大,而且非常容易使用,效率也还不错,实现大多数的数学运算完全没有问题,自己没有必要从头开始编写解释器。有人已经建立了一条康庄大道,何必再走自己的泥泞小路呢?


五:解析器模式的案例
 【1】抽象表达式类

 1 package com.yeepay.sxf.template22;
 2 
 3 import java.util.HashMap;
 4 
 5 /**
 6  * 抽象表达式类
 7  * @author sxf
 8  *
 9  */
10 public abstract class Expression {
11 
12     //解析公式和数值,其中var中的key值是公式中的参数,value值是具体的数字
13     public abstract int interpreter(HashMap<String, Integer> var);
14 }
View Code

 【2】变量解析器

 1 package com.yeepay.sxf.template22;
 2 
 3 import java.util.HashMap;
 4 
 5 /**
 6  * 变量解析器
 7  * (运算公式参数)
 8  * @author sxf
 9  *
10  */
11 public class VarExpression extends Expression{
12     //公式中参数名的字母
13     //从HashMap<String,Object>中的key
14     private String key;
15 
16     public VarExpression(String key) {
17         super();
18         this.key = key;
19     }
20 
21     @Override
22     public int interpreter(HashMap<String, Integer> var) {
23         return var.get(this.key);
24     }
25     
26 }
View Code

 【3】抽象运算符号解析器

 1 package com.yeepay.sxf.template22;
 2 /**
 3  * 抽象运算符号解析器
 4  * @author sxf
 5  *
 6  */
 7 public abstract class SymbolExpression  extends Expression{
 8     //运算符号左侧的表达式
 9     protected Expression left;
10     //运算符号右侧的表达式
11     protected Expression right;
12     
13     //所有的解析公式都应只关心自己左右两个表达式的结果
14     public SymbolExpression(Expression left,Expression right){
15         this.left=left;
16         this.right=right;
17     }
18 }
View Code

 【4】加法解析器

 1 package com.yeepay.sxf.template22;
 2 
 3 import java.util.HashMap;
 4 
 5 /**
 6  * 加法解析器
 7  * @author sxf
 8  *
 9  */
10 public class AddExpression extends SymbolExpression{
11 
12     public AddExpression(Expression left, Expression right) {
13         super(left, right);
14     }
15 
16     /**
17      * 返回运算结果
18      */
19     @Override
20     public int interpreter(HashMap<String, Integer> var) {
21         //从参数中解析出加数a
22         int a=super.left.interpreter(var);
23         //从参数中解析出家数b
24         int b=super.right.interpreter(var);
25         //返回运算结果
26          return a+b;
27     }
28 
29     
30 }
View Code

 【5】减法解析器

 1 package com.yeepay.sxf.template22;
 2 
 3 import java.util.HashMap;
 4 /**
 5  * 减法解析器
 6  * @author sxf
 7  *
 8  */
 9 public class SubExpression extends SymbolExpression{
10 
11     
12     public SubExpression(Expression left, Expression right) {
13         super(left, right);
14     }
15 
16     @Override
17     public int interpreter(HashMap<String, Integer> var) {
18         //解析出被减数
19         int left=super.left.interpreter(var);
20         //解析出减数
21         int right=super.right.interpreter(var);
22          //返回运算结果
23         return left-right;
24     }
25 
26     
27 }
View Code

 【6】解析器封装类

 1 package com.yeepay.sxf.template22;
 2 
 3 import java.util.HashMap;
 4 import java.util.Stack;
 5 
 6 /**
 7  * 解析器封装类
 8  * @author sxf
 9  *
10  */
11 public class Calculator {
12 
13     //定义表达式
14     private Expression expression;
15 
16     
17     //构造函数传参,并解析
18     public Calculator(String expStr){
19         //定义一个栈,安排运算的先后顺序
20         Stack<Expression> stack=new Stack<Expression>();
21         //表达式拆分为字符数组
22         char[] charArray=expStr.toCharArray();
23         //运算
24         Expression left=null;
25         Expression right=null;
26         for(int i=0;i<charArray.length;i++){
27             switch (charArray[i]) {
28             case '+'://加法
29                 left=stack.pop();
30                 right=new VarExpression(String.valueOf(charArray[++i]));
31                 stack.push(new AddExpression(left, right));
32                 break;
33             case '-'://减法    
34                 left=stack.pop();
35                 right=new VarExpression(String.valueOf(charArray[++i]));
36                 stack.push(new SubExpression(left, right));
37                 break;
38             default:
39                 stack.push(new VarExpression(String.valueOf(charArray[i])));
40                 break;
41             }
42         }
43         this.expression=stack.pop();
44     }
45     
46     //开始运算
47     public int run(HashMap<String, Integer> var){
48         return this.expression.interpreter(var);
49     }
50 }
View Code

【7】客户端测试

  1 package com.yeepay.sxf.template22;
  2 
  3 import java.io.BufferedReader;
  4 import java.io.IOException;
  5 import java.io.InputStreamReader;
  6 import java.util.HashMap;
  7 import java.util.Stack;
  8 
  9 /**
 10  * 客户端测试
 11  * @author sxf
 12  *
 13  */
 14 public class Client {
 15 
 16     //运行四则运算
 17     public static void main(String[] args) {
 18     
 19         test01();
 20         
 21     }
 22     
 23     
 24     /**
 25      * 测试堆栈
 26      */
 27     public static void test02(){
 28         Stack<String> aStack=new Stack<String>();
 29         aStack.add("1");
 30         aStack.add("2");
 31         aStack.add("3");
 32         
 33         String aString=aStack.peek();
 34         System.out.println("Client.main()"+aString);
 35         
 36         //aStack.clear();
 37         boolean flag=aStack.empty();
 38         System.out.println("Client.main()"+flag);
 39         
 40         System.out.println("Client.main()"+aStack.pop());
 41         System.out.println("Client.main()"+aStack.pop());
 42         System.out.println("Client.main()"+aStack.pop());
 43         
 44     }
 45     
 46     
 47     public static void test01(){
 48         String expStr;
 49         try {
 50             expStr = getExpStr();
 51             System.out.println("Client.main()"+expStr);
 52             HashMap<String, Integer> var=getValue(expStr);
 53             Calculator cal=new Calculator(expStr);
 54             System.out.println("Client.main(运算结果为:"+expStr+"="+cal.run(var));
 55         } catch (IOException e) {
 56             // TODO Auto-generated catch block
 57             e.printStackTrace();
 58         }
 59         
 60     }
 61     
 62     //获得表达式
 63     public static String getExpStr() throws IOException{
 64         System.out.println("请输入表达式");
 65         return (new BufferedReader(new InputStreamReader(System.in))).readLine();
 66     }
 67     
 68     
 69     public static HashMap<String, Integer> getValue(String exprStr) throws IOException{
 70         HashMap<String, Integer> map=new HashMap<String, Integer>();
 71         //解析有几个参数要传递
 72         for(char ch:exprStr.toCharArray()){
 73             if(ch !='+' && ch!='-'){
 74                 //解决重复参数的问题
 75                 if(!map.containsKey(String.valueOf(ch))){
 76                     String in=(new BufferedReader(new InputStreamReader(System.in))).readLine();
 77                     map.put(String.valueOf(ch), Integer.valueOf(in));
 78                 }
 79             }
 80         }
 81         return map;
 82     } 
 83     
 84     
 85     
 86     
 87     
 88     
 89     
 90     
 91     
 92     
 93     
 94     
 95     
 96     
 97     
 98     
 99     
100     
101     
102     
103 }
View Code

 

相关文章
|
1月前
|
设计模式 SQL 算法
设计模式了解哪些,模版模式
设计模式了解哪些,模版模式
22 0
|
2月前
|
设计模式 Java uml
C++设计模式之 依赖注入模式探索
C++设计模式之 依赖注入模式探索
51 0
|
27天前
|
设计模式 Java 数据库
小谈设计模式(2)—简单工厂模式
小谈设计模式(2)—简单工厂模式
|
7天前
|
设计模式 前端开发 Java
19:Web开发模式与MVC设计模式-Java Web
19:Web开发模式与MVC设计模式-Java Web
19 4
|
10天前
|
设计模式 算法 搜索推荐
【PHP开发专栏】PHP设计模式解析与实践
【4月更文挑战第29天】本文介绍了设计模式在PHP开发中的应用,包括创建型(如单例、工厂模式)、结构型和行为型模式(如观察者、策略模式)。通过示例展示了如何在PHP中实现这些模式,强调了它们在提升代码可维护性和可扩展性方面的作用。设计模式是解决常见问题的最佳实践,但在使用时需避免过度设计,根据实际需求选择合适的设计模式。
|
12天前
|
设计模式 消息中间件 Java
Java 设计模式:探索发布-订阅模式的原理与应用
【4月更文挑战第27天】发布-订阅模式是一种消息传递范式,被广泛用于构建松散耦合的系统。在 Java 中,这种模式允许多个对象监听和响应感兴趣的事件。
32 2
|
12天前
|
设计模式 Java
Java 设计模式:工厂模式与抽象工厂模式的解析与应用
【4月更文挑战第27天】设计模式是软件开发中用于解决常见问题的典型解决方案。在 Java 中,工厂模式和抽象工厂模式是创建型模式中非常核心的模式,它们主要用于对象的创建,有助于增加程序的灵活性和扩展性。本博客将详细介绍这两种模式的概念、区别以及如何在实际项目中应用这些模式。
16 1
|
15天前
|
设计模式 存储 JavaScript
[设计模式Java实现附plantuml源码~创建型] 多态工厂的实现——工厂方法模式
[设计模式Java实现附plantuml源码~创建型] 多态工厂的实现——工厂方法模式
|
15天前
|
设计模式 Java Go
[设计模式Java实现附plantuml源码~创建型] 集中式工厂的实现~简单工厂模式
[设计模式Java实现附plantuml源码~创建型] 集中式工厂的实现~简单工厂模式
|
15天前
|
设计模式 JavaScript 开发者
Vue的混入(Mixins):混入的使用和设计模式解析
【4月更文挑战第24天】Vue Mixins是实现组件复用的灵活工具,允许共享可复用功能。混入对象包含组件选项,如数据、方法和生命周期钩子,可被合并到使用它的组件中。通过组合模式和钩子注入模式,混入能提高代码复用和可维护性。然而,注意命名冲突、选项合并策略以及慎用全局混入以防止副作用。正确使用混入能提升开发效率和软件质量。

推荐镜像

更多