精华内容
下载资源
问答
  • JAVA设计模式--解释器模式

    万次阅读 2017-02-25 14:51:01
    目录 一、什么是解释器模式 ...给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。 所谓“语言”,指的是使用规定格式和文法的一类字符组合。 ...

    目录

    一、什么是解释器模式

    二、解释器模式的结构

    三、解释器模式应用举例

    四、解释器模式的适用性

    五、解释器模式的优缺点

    六、相关模式

    七、总结


    一、什么是解释器模式

    解释器(Interpreter)模式是一种对象的行为模式。给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

    所谓“语言”,指的是使用规定格式和文法的一类字符组合。

    所谓“文法”,简单点说就是我们俗称的“语法规则”。

    以java语言为例,其变量声明文法为:[public|protected|private]+[static]+[final]+变量类型+变量名+[=初始值],以下这条字符串变量声明语句就是该文法的一种表示:private static final String sql=" select * from user where 1=1" 。

    解释器模式的本质:分离实现,解释执行

    设计意图:为语言中的不同的文法表示,分别定义一个与该文法表示相对应的的解释器,然后通过这个解释器来对该文法表示进行解释。  

    二、解释器模式的结构

    解释器模式涉及的角色及其职责如下:

    抽象表达式(AbstractExpression)角色:约定解释器的解释操作,主要是一个interpret()方法。
               终结符表达式(TerminalExpression)角色:用来实现文法中和终结符相关的解释操作,不再包含其它的解释器,如果用组合模式来构建抽象语法树的话,就相当于组合模式中的叶子对象,可以有多种终结符解释器。
               非终结符表达式(NonterminalExpression)角色:用来实现文法中和非终结符相关的解释操作,通常一个解释器对应一个语法规则,可以包含其它的解释器,如果用组合模式来构建抽象语法树的话,就相当于组合模式中的组合对象,可以有多种非终结符解释器。
               环境(Context)角色:也称“上下文”,常用HashMap来代替,通常包含解释器之外的一些全局信息(解释器需要的数据,或是公共的功能)。
               客户端(Client)角色:构建文法表示的抽象语法树(Abstract Syntax Tree,该抽象语法树由终结符表达式和非终结符表达式的实例装配而成),并调用解释操作interpret()方法。

    解释器模式结构示意源代码如下:

    首先定义一个抽象表达式(AbstractExpression)角色,并在其中定义一个执行解释操作的接口,示例代码如下。

    /**
     * 抽象表达式
     */
    public abstract class AbstractExpression {
    
    	/**
    	 * 解释的操作
    	 * @param ctx 上下文对象
    	 */
    	public abstract void interpret(Context ctx);
    
    }

    抽象表达式(AbstractExpression) 的具体实现分两种:终结符表达式(TerminalExpression)和非终结符表达式 

    /**
     * 终结符表达式
     */
    public class TerminalExpression extends AbstractExpression {
    
    	@Override
    	public void interpret(Context ctx) {
    		// 实现与语法规则中的终结符相关联的解释操作
    
    	}
    
    }
    /**
     * 非终结符表达式
     */
    public class NonterminalExpression extends AbstractExpression {
    
    	@Override
    	public void interpret(Context ctx) {
    		// 实现与语法规则中的非终结符相关联的解释操作
    
    	}
    
    }

    再来看看环境(Context)的定义,示例代码如下。  

    /**
     * 上下文,包含解释器之外的一些全局信息
     */
    public class Context {
    
    }

    最后来看看客户端的定义,示例代码如下。  

    /**
     * 使用解释器的客户
     */
    public class Client {
    	/**
    	 * 主要按照语法规格对特定的句子构建抽象语法树
    	 * 然后调用解释操作
    	 */
    
    }

    以上示例代码很简单, 只是为了说明解释器模式实现的基本结构和各个角色的功能, 实际的解释逻辑并未写出。  

    三、解释器模式应用举例

    假如我们要实现这么一个功能:加减法计算器。

    计算器中参与运算的元素为a、b、c、d等...,这些元素都与具体的业务相关,我们不细追究其所代表的意义。现要实现:客户端指定一个公式,如“a+b-c”,“a+b+c-d”,计算器就可以计算出结果。

    设计好的类图结构示意如下: 

    源代码如下。 
     

    import java.util.HashMap;
    
    /**
     * 抽象表达式,声明解释操作
     */
    public interface AbstractExpression {
    
    	// 每个表达式都必须有一个解释操作
    	public int interprete(HashMap<String, Integer> var);
    
    }
    import java.util.HashMap;
    
    /**
     * 终结符表达式,代表参加运算的元素对象
     */
    public class VarExpression implements AbstractExpression {
    
    	private String key;
    
    	public VarExpression(String key) {
    		this.key = key;
    	}
    
    	public int interprete(HashMap<String, Integer> var) {
    		return (Integer) var.get(this.key);
    	}
    }
    /**
     * 非终结符表达式,运算符(此处为加法和减法)的抽象父类,真正的解释操作由其子类来实现
     */
    public abstract class SymbolExpression implements AbstractExpression {
    
    	protected AbstractExpression left;
    	protected AbstractExpression right;
    
    	// 非终结符表达式的解释操作只关心自己左右两个表达式的结果
    	public SymbolExpression(AbstractExpression left, AbstractExpression right) {
    		this.left = left;
    		this.right = right;
    	}
    
    }
    import java.util.HashMap;
    
    /**
     * 加法表达式
     */
    public class AddExpression extends SymbolExpression {
    
    	public AddExpression(AbstractExpression left, AbstractExpression right) {
    		super(left, right);
    	}
    
    	// 把左右两个表达式运算的结果加起来
    	public int interprete(HashMap<String, Integer> var) {
    		return super.left.interprete(var) + super.right.interprete(var);
    	}
    }
    import java.util.HashMap;
    
    /**
     * 减法表达式
     */
    public class SubExpression extends SymbolExpression {
    
    	public SubExpression(AbstractExpression left, AbstractExpression right) {
    		super(left, right);
    	}
    
    	// 左右两个表达式相减
    	public int interprete(HashMap<String, Integer> var) {
    		return super.left.interprete(var) - super.right.interprete(var);
    	}
    }
    import java.util.HashMap;
    import java.util.Stack;
    
    public class Calculator {
    
    	private AbstractExpression expression;
    
    	/**
    	 * 对公式进行解析操作
    	 * 
    	 * @param expStr
    	 *            输入的公式
    	 */
    	public Calculator(String expStr) {
    		// 定义一个堆栈,安排运算的先后顺序
    		Stack<AbstractExpression> stack = new Stack<AbstractExpression>();
    		// 表达式拆分为字符数组
    		char[] charArray = expStr.toCharArray();
    		// 运算
    		AbstractExpression left = null;
    		AbstractExpression right = null;
    		for (int i = 0; i < charArray.length; i++) {
    			switch (charArray[i]) {
    			case '+': // 加法
    				left = stack.pop();
    				right = new VarExpression(String.valueOf(charArray[++i]));
    				stack.push(new AddExpression(left, right));
    				break;
    
    			case '-': // 减法
    				left = stack.pop();
    				right = new VarExpression(String.valueOf(charArray[++i]));
    				stack.push(new SubExpression(left, right));
    				break;
    			default: // 公式中的变量
    				stack.push(new VarExpression(String.valueOf(charArray[i])));
    			}
    		}
    		// 把运算结果抛出来
    		this.expression = stack.pop();
    	}
    
    	// 计算结果
    	public int calculate(HashMap<String, Integer> var) {
    		return this.expression.interprete(var);
    	}
    }
    import java.util.HashMap;
    
    public class Client {
    	public static void main(String[] args) {
    		// 构造运算元素的值列表
    		HashMap<String, Integer> ctx = new HashMap<String, Integer>();
    		ctx.put("a", 10);
    		ctx.put("b", 20);
    		ctx.put("c", 30);
    		ctx.put("d", 40);
    		ctx.put("e", 50);
    		ctx.put("f", 60);
    		Calculator calc = new Calculator("a+b-c");
    		int result = calc.calculate(ctx);
    		System.out.println("Result of a+b-c: " + result);
    		calc = new Calculator("d-a-b+c");
    		result = calc.calculate(ctx);
    		System.out.println("Result of d-a-b+c: " + result);
    	}
    }

    运行程序打印结果如下:

    Result of a+b-c: 0
    Result of d-a-b+c: 40

    该加减法计算器示例与解释器模式组成元素对照如下:
                ①给定一种语言, 本例中就是一个简单的加减运算。
                ②定义一种文法表示,本例中就是指定的参与运算的元素(abcdef)以及运算符(+-),以及由它们构造而成的公式,如 d-a-b+c。
                ③给定一个解释器来解释语言中的句子:本例中的解释器是多个类的组合,包括Calculator和AbstractExpression 。
                ④TerminalExpression表示终结符表达式,相当于本例中的VarExpression。
                ⑤NonterminalExpression是非终结符表达式,相当于本例中的加法、减法。

     

    四、解释器模式的适用性

    解释器模式似乎使用面不是很广,它描述了一个语言解释器是如何构成的,在实际应用中,我们可能很少去构造一个语言的文法。

    建议在以下情况中选用解释器模式:
               当有一个语言需要解释执行,并且可以将语言中的句子表示为一个抽象语法树的时候,可以考虑使用解释器模式。  

    五、解释器模式的优缺点

    解释器模式有以下优点:
               1) 易于实现语法 
               在解释器模式中,一条语法规则用一个解释器对象来解释执行。对于解释器的实现来讲,功能就变得比较简单,只需要考虑这一条语法规则的实现就可以了,其他的都不用管。
               2) 易于扩展新的语法
               正是由于采用一个解释器对象负责一条语法规则的方式,使得扩展新的语法非常容易。扩展了新的语法,只需要创建相应的解释器对象,在创建抽象语法树的时候使用这个新的解释器对象就可以了。

    解释器模式有以下缺点:
               不适合复杂的语法
               如果语法特别复杂,构建解释器模式需要的抽象语法树的工作是非常艰巨的,再加上有可能会需要构建多个抽象语法树。所以解释器模式不太适合于复杂的语法,对于复杂的语法,使用语法分析程序或编译器生成器可能会更好一些。  

    六、相关模式

    (1)解释器和组合模式
               这两种模式可以组合使用。通常解释器模式都会使用组合模式来实现,这样能够方便地构建抽象语法树。一般非终结符解释器相当于组合模式中的组合对象,终结符解释器相当于叶子对象。
               (2)解释器模式和迭代器模式
               这两种模式可以组合使用。由于解释器模式通常使用组合模式来实现,因此在遍历整个对象结构时,可以使用迭代器模式。
               (3)解释器模式和享元模式
               这两种模式可以组合使用。在使用解释器模式的时候,可能会造成多个细粒度对象,如各式各样的终结符解释器,而这些终结符解释器对不同的表达式来说是一样的,是可以共用的,因此可以引入享元模式来共享这些对象。
               (4)解释器模式和访问者模式
               这两种模式可以组合使用。在解释器模式中,语法规则和解释器对象是有对应关系的。语法规则的变动意味着功能的变化。自然会导致使用不同的解释器对象;而且一个语法规则可以被不同的解释器解释执行。因此在构建抽象语法树的时候,如果每个节点所对应的解释器对象是固定的,这意味着该节点对应的功能是固定的,那么就不得不根据需要来构建不同的抽象语法树。为了让构建的抽象语法树较为通用,那就要求解释器的功能不要那么固定,要能很方便地改变解释器的功能,这个时候就变成了如何能够很方便地更改树形结构中节点对象的功能了,访问者模式可以很好的实现这个功能。  

    七、总结

    解释器模式通过一个解释器对象处理一个语法规则的方式,把复杂的功能分离开;然后选择需要被执行的功能,并把这些功能组合成为需要被解释执行的抽象语法树;再按照抽象语法树来解释执行,实现相应的功能。  

    展开全文
  • 设计模式——解释器模式

    万次阅读 2018-04-08 08:08:48
    解释器模式顾名思义,就是给定句子和文法,然后进行解释的一种设计模式。是一种应用很少的模式,涉及到文法、语义等东西,没有基础很不好理解,更容易把设计的模式变得一坨一坨的,不容易维护更不容易理解。解释器...

    解释器模式顾名思义,就是给定句子和文法,然后进行解释的一种设计模式。是一种应用很少的模式,涉及到文法、语义等东西,没有基础很不好理解,更容易把设计的模式变得一坨一坨的,不容易维护更不容易理解。

    解释器模式涉及到四个角色:

    抽象表达式(Expression)角色:声明一个所有的具体表达式角色都需要实现的抽象接口。这个接口主要是一个interpret()方法,称做解释操作。

    终结符表达式(Terminal Expression)角色:实现了抽象表达式角色所要求的接口,主要是一个interpret()方法;文法中的每一个终结符都有一个具体终结表达式与之相对应。比如有一个简单的公式R=R1+R2,在里面R1和R2就是终结符,对应的解析R1和R2的解释器就是终结符表达式。

    非终结符表达式(Nonterminal Expression)角色:文法中的每一条规则都需要一个具体的非终结符表达式,非终结符表达式一般是文法中的运算符或者其他关键字,比如公式R=R1+R2中,“+"就是非终结符,解析“+”的解释器就是一个非终结符表达式。

    环境(Context)角色:这个角色的任务一般是用来存放文法中各个终结符所对应的具体值,比如R=R1+R2,我们给R1赋值100,给R2赋值200。这些信息需要存放到环境角色中,很多情况下我们使用Map来充当环境角色就足够了。

    意图:给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。

    主要解决:对于一些固定文法构建一个解释句子的解释器。

    何时使用:如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。

    如何解决:构件语法树,定义终结符与非终结符。

    关键代码:构件环境类,包含解释器之外的一些全局信息,一般是 HashMap。

    应用实例:编译器、运算表达式计算。

    优点:1、可扩展性比较好,灵活。2、增加了新的解释表达式的方式。3、易于实现简单文法。

    缺点:1、可利用场景比较少。2、对于复杂的文法比较难维护。3、解释器模式会引起类膨胀。4、解释器模式采用递归调用方法。

    使用场景:1、可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。2、一些重复出现的问题可以用一种简单的语言来进行表达。3、一个简单语法需要解释的场景。

    注意事项:可利用场景比较少,JAVA 中如果碰到可以用 expression4J 代替。

    由于这个模式很不容易理解,所以写了一个很简单的例子,自增和自减的方法:

    <?php
    //上下文类
    class Context{
        public $Num;
        function Set($Num)
        {
            $this->Num = $Num;
        }
        function Get()
        {
            return $this->Num;
        }
    }
    
    //抽象解释器
    abstract class AbstractExpreesion{
        abstract function Interpret(Context $Context);
    }
    
    //自加解释器
    class PlusExpression extends AbstractExpreesion{
        function Interpret(Context $Context)
        {
            $Context->Num = $Context->Num+1;
        }
    }
    
    //自减解释器
    class MinusExpression extends AbstractExpreesion{
        function Interpret(Context $Context)
        {
            $Context->Num = $Context->Num-1;
        }
    }
    
    //调用
    //上下文
    $Context = new Context();
    $Context->Set(100);
    //加减法
    $PlusExpression = new  PlusExpression();
    $MinusExpression = new MinusExpression();
    //两次自加一次自减
    $PlusExpression->Interpret($Context);
    $PlusExpression->Interpret($Context);
    $MinusExpression->Interpret($Context);
    //输出
    echo $Context->Get();
    
    ?>

    展开全文
  • 设计模式 | 解释器模式及典型应用

    千次阅读 2019-01-13 23:47:54
    解释器模式(Interpreter Pattern):定义一个语言的文法,并且建立一个解释器来解释该语言中的句子,这里的 “语言” 是指使用规定格式和语法的代码。解释器模式是一种类行为型模式。 角色 AbstractExpre...

    微信原文:设计模式 | 解释器模式及典型应用
    博客原文:设计模式 | 解释器模式及典型应用

    本文主要介绍解释器模式,在日常开发中,解释器模式的使用频率比较低

    解释器模式

    解释器模式(Interpreter Pattern):定义一个语言的文法,并且建立一个解释器来解释该语言中的句子,这里的 “语言” 是指使用规定格式和语法的代码。解释器模式是一种类行为型模式。

    角色

    AbstractExpression(抽象表达式):在抽象表达式中声明了抽象的解释操作,它是所有终结符表达式和非终结符表达式的公共父类。

    TerminalExpression(终结符表达式):终结符表达式是抽象表达式的子类,它实现了与文法中的终结符相关联的解释操作,在句子中的每一个终结符都是该类的一个实例。通常在一个解释器模式中只有少数几个终结符表达式类,它们的实例可以通过非终结符表达式组成较为复杂的句子。

    NonterminalExpression(非终结符表达式):非终结符表达式也是抽象表达式的子类,它实现了文法中非终结符的解释操作,由于在非终结符表达式中可以包含终结符表达式,也可以继续包含非终结符表达式,因此其解释操作一般通过递归的方式来完成。

    Context(环境类):环境类又称为上下文类,它用于存储解释器之外的一些全局信息,通常它临时存储了需要解释的语句。

    解释器模式结构图

    示例

    使用解释器模式实现一个简单的后缀表达式解释器,仅支持对整数的加法和乘法即可

    定义抽象表达式接口

    public interface Interpreter {
        int interpret();
    }
    

    非终结符表达式,对整数进行解释

    public class NumberInterpreter implements Interpreter {
        private int number;
    
        public NumberInterpreter(int number) {
            this.number = number;
        }
    
        public NumberInterpreter(String number) {
            this.number = Integer.parseInt(number);
        }
    
        @Override
        public int interpret() {
            return this.number;
        }
    }
    

    终结符表达式,对加法和乘法进行解释

    // 加法
    public class AddInterpreter implements Interpreter {
        private Interpreter firstExpression, secondExpression;
        public AddInterpreter(Interpreter firstExpression, Interpreter secondExpression) {
            this.firstExpression = firstExpression;
            this.secondExpression = secondExpression;
        }
        @Override
        public int interpret() {    
            return this.firstExpression.interpret() + this.secondExpression.interpret();
        }
        @Override
        public String toString() {
            return "+";
        }
    }
    
    // 乘法
    public class MultiInterpreter implements Interpreter {
        private Interpreter firstExpression, secondExpression;
    
        public MultiInterpreter(Interpreter firstExpression, Interpreter secondExpression) {
            this.firstExpression = firstExpression;
            this.secondExpression = secondExpression;
        }
        @Override
        public int interpret() {
            return this.firstExpression.interpret() * this.secondExpression.interpret();
        }
        @Override
        public String toString() {
            return "*";
        }
    }
    

    工具类

    public class OperatorUtil {
        public static boolean isOperator(String symbol) {
            return (symbol.equals("+") || symbol.equals("*"));
    
        }
        public static Interpreter getExpressionObject(Interpreter firstExpression, Interpreter secondExpression, String symbol) {
            if ("+".equals(symbol)) {  // 加法
                return new AddInterpreter(firstExpression, secondExpression);
            } else if ("*".equals(symbol)) {    // 乘法
                return new MultiInterpreter(firstExpression, secondExpression);
            } else {
                throw new RuntimeException("不支持的操作符:" + symbol);
            }
        }
    }
    

    测试,对后缀表达式 6 100 11 + * 进行求值

    public class Test {
        public static void main(String[] args) {
            String inputStr = "6 100 11 + *";
            MyExpressionParser expressionParser = new MyExpressionParser();
            int result = expressionParser.parse(inputStr);
            System.out.println("解释器计算结果: " + result);
        }
    }
    

    运行结果

    入栈: 6
    入栈: 100
    入栈: 11
    出栈: 11 和 100
    应用运算符: +
    阶段结果入栈: 111
    出栈: 111 和 6
    应用运算符: *
    阶段结果入栈: 666
    解释器计算结果: 666
    

    示例.类图

    解释器模式总结

    解释器模式为自定义语言的设计和实现提供了一种解决方案,它用于定义一组文法规则并通过这组文法规则来解释语言中的句子。虽然解释器模式的使用频率不是特别高,但是它在正则表达式XML文档解释等领域还是得到了广泛使用。

    主要优点

    • 易于改变和扩展文法。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
    • 每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言
    • 实现文法较为容易。在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂,还可以通过一些工具自动生成节点类代码。
    • 增加新的解释表达式较为方便。如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式类,原有表达式类代码无须修改,符合 “开闭原则”。

    主要缺点

    • 对于复杂文法难以维护。在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护,此时可以考虑使用语法分析程序等方式来取代解释器模式。
    • 执行效率较低。由于在解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦。

    适用场景

    • 可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。
    • 一些重复出现的问题可以用一种简单的语言来进行表达。
    • 一个语言的文法较为简单。
    • 对执行效率要求不高。

    解释器模式的典型应用

    Spring EL表达式中的解释器模式

    Spring EL表达式相关的类在 org.springframework.expression 包下,类图如下

    org.springframework.expression 包的类图

    涉及的类非常多,这里仅对此时我们最关心的几个类做介绍:

    SpelExpression,表示一个 EL 表达式,表达式在内部通过一个 AST抽象语法树 表示,EL表达式求值是通过 this.ast.getValue(expressionState); 求值

    public class SpelExpression implements Expression {
    	private final String expression;
    	private final SpelNodeImpl ast;
    	private final SpelParserConfiguration configuration;
    	
    	@Override
    	@Nullable
    	public Object getValue() throws EvaluationException {
    		if (this.compiledAst != null) {
    			try {
    				EvaluationContext context = getEvaluationContext();
    				return this.compiledAst.getValue(context.getRootObject().getValue(), context);
    			}
    			catch (Throwable ex) {
    				// If running in mixed mode, revert to interpreted
    				if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
    					this.interpretedCount = 0;
    					this.compiledAst = null;
    				}
    				else {
    					// Running in SpelCompilerMode.immediate mode - propagate exception to caller
    					throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
    				}
    			}
    		}
    
    		ExpressionState expressionState = new ExpressionState(getEvaluationContext(), this.configuration);
    		Object result = this.ast.getValue(expressionState);
    		checkCompile(expressionState);
    		return result;
    	}
    	//...省略...
    }
    

    SpelNodeImpl:已解析的Spring表达式所代表的ast语法树的节点的通用父类型,语法树的节点在解释器模式中扮演的角色是终结符和非终结符。从类图中可以看到,SpelNodeImpl 的子类主要有 Literal,Operator,Indexer等,其中 Literal 是各种类型的值的父类,Operator 则是各种操作的父类

    public abstract class SpelNodeImpl implements SpelNode, Opcodes {
    	protected int pos;  // start = top 16bits, end = bottom 16bits
    	protected SpelNodeImpl[] children = SpelNodeImpl.NO_CHILDREN;
    	@Nullable
    	private SpelNodeImpl parent;
    
    	public final Object getValue(ExpressionState expressionState) throws EvaluationException {
    		return getValueInternal(expressionState).getValue();
    	}
        // 抽象方法,由子类实现,获取对象的值
    	public abstract TypedValue getValueInternal(ExpressionState expressionState) throws EvaluationException;
    	//...省略...
    }
    

    IntLiteral 表示整型文字的表达式语言的ast结点

    public class IntLiteral extends Literal {
        private final TypedValue value;
    	public IntLiteral(String payload, int pos, int value) {
    		super(payload, pos);
    		this.value = new TypedValue(value); // 
    		this.exitTypeDescriptor = "I";
    	}
    	@Override
    	public TypedValue getLiteralValue() {
    		return this.value;
    	}
    	// ...
    }
    

    OpPlus 表示加法的ast结点,在 getValueInternal 方法中对操作符两边进行相加操作

    public class OpPlus extends Operator {
    	public OpPlus(int pos, SpelNodeImpl... operands) {
    		super("+", pos, operands);
    		Assert.notEmpty(operands, "Operands must not be empty");
    	}
    	@Override
    	public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
    		SpelNodeImpl leftOp = getLeftOperand();
    
    		if (this.children.length < 2) {  // if only one operand, then this is unary plus
    			Object operandOne = leftOp.getValueInternal(state).getValue();
    			if (operandOne instanceof Number) {
    				if (operandOne instanceof Double) {
    					this.exitTypeDescriptor = "D";
    				}
    				else if (operandOne instanceof Float) {
    					this.exitTypeDescriptor = "F";
    				}
    				else if (operandOne instanceof Long) {
    					this.exitTypeDescriptor = "J";
    				}
    				else if (operandOne instanceof Integer) {
    					this.exitTypeDescriptor = "I";
    				}
    				return new TypedValue(operandOne);
    			}
    			return state.operate(Operation.ADD, operandOne, null);
    		}
            // 递归调用leftOp的 getValueInternal(state) ,获取操作符左边的值
    		TypedValue operandOneValue = leftOp.getValueInternal(state);
    		Object leftOperand = operandOneValue.getValue();
    		// 递归调用children[1]的 getValueInternal(state) ,获取操作符右边的值
    		TypedValue operandTwoValue = getRightOperand().getValueInternal(state);
    		Object rightOperand = operandTwoValue.getValue();
    
            // 如果操作符左右都是数值类型,则将它们相加
    		if (leftOperand instanceof Number && rightOperand instanceof Number) {
    			Number leftNumber = (Number) leftOperand;
    			Number rightNumber = (Number) rightOperand;
                
    			if (leftNumber instanceof BigDecimal || rightNumber instanceof BigDecimal) {
    				BigDecimal leftBigDecimal = NumberUtils.convertNumberToTargetClass(leftNumber, BigDecimal.class);
    				BigDecimal rightBigDecimal = NumberUtils.convertNumberToTargetClass(rightNumber, BigDecimal.class);
    				return new TypedValue(leftBigDecimal.add(rightBigDecimal));
    			}
    			else if (leftNumber instanceof Double || rightNumber instanceof Double) {
    				this.exitTypeDescriptor = "D";  
    				return new TypedValue(leftNumber.doubleValue() + rightNumber.doubleValue());
    			}
    			//...省略 Float->F, BigInteger->add, Long->J,Integer->I
    			else {
    				// Unknown Number subtypes -> best guess is double addition
    				return new TypedValue(leftNumber.doubleValue() + rightNumber.doubleValue());
    			}
    		}
    		//...
    		return state.operate(Operation.ADD, leftOperand, rightOperand);
    	}
        //...
    }
    

    通过一个示例,调试查看程序中间经历的步骤

    public class SpringELTest {
        public static void main(String[] args) {
            // 1. 构建解析器
            org.springframework.expression.ExpressionParser parser = new SpelExpressionParser();
            // 2. 解析表达式
            Expression expression = parser.parseExpression("100 * 2 + 400 * 1 + 66");
            // 3. 获取结果
            int result = (Integer) expression.getValue();
            System.out.println(result); // 结果:666
        }
    }
    

    EL表达式解析后得到表达式 (((100 * 2) + (400 * 1)) + 66)
    EL表达式解析后得到的表达式

    如果用图形把其这棵AST抽象语法树简单地画出来,大概是这样

    示例.AST抽象语法树

    调用 expression.getValue() 求值,此时的 ast 是语法树的头结点,也就是 + OpPlus,所以通过 this.ast.getValue(expressionState) 进入了 OpPlus 的 getValue 方法(是父类中的方法),接着进入 getValueInternal 方法,然后递归计算操作符左边的值,递归计算操作符右边的值,最后相加返回

    示例.spring EL调试

    参考:
    刘伟.Java设计模式
    Java设计模式精讲

    后记

    欢迎评论、转发、分享

    更多内容可访问我的个人博客:http://laijianfeng.org

    关注【小旋锋】微信公众号,及时接收博文推送

    关注_小旋锋_微信公众号

    展开全文
  • Java设计模式-解释器模式

    千次阅读 2019-03-28 09:01:26
    解释器模式

    解释器模式

      在软件开发中,会遇到有些问题多次重复出现,而且有一定的相似性和规律性。如果将它们归纳成一种简单的语言,那么这些问题实例将是该语言的一些句子,这样就可以用“编译原理”中的解释器模式来实现了。

      虽然使用解释器模式的实例不是很多,但对于满足以上特点,且对运行效率要求不是很高的应用实例,如果用解释器模式来实现,其效果是非常好的,本文将介绍其工作原理与使用方法。

    解释器模式的定义与特点

      解释器(Interpreter)模式的定义:给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。也就是说,用编译语言的方式来分析应用中的实例。这种模式实现了文法表达式处理的接口,该接口解释一个特定的上下文。

      这里提到的文法和句子的概念同编译原理中的描述相同,“文法”指语言的语法规则,而“句子”是语言集中的元素。例如,汉语中的句子有很多,“我是中国人”是其中的一个句子,可以用一棵语法树来直观地描述语言中的句子。

      解释器模式是一种类行为型模式,其主要优点如下。

    • 扩展性好。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
    • 容易实现。在语法树中的每个表达式节点类都是相似的,所以实现其文法较为容易。

      解释器模式的主要缺点如下。

    • 执行效率较低。解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦。
    • 会引起类膨胀。解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护。
    • 可应用的场景比较少。在软件开发中,需要定义语言文法的应用实例非常少,所以这种模式很少被使用到。

    解释器模式的结构与实现

      解释器模式常用于对简单语言的编译或分析实例中,为了掌握好它的结构与实现,必须先了解编译原理中的“文法、句子、语法树”等相关概念。

    1) 文法

      文法是用于描述语言的语法结构的形式规则。没有规矩不成方圆,例如,有些人认为完美爱情的准则是“相互吸引、感情专一、任何一方都没有恋爱经历”,虽然最后一条准则较苛刻,但任何事情都要有规则,语言也一样,不管它是机器语言还是自然语言,都有它自己的文法规则。例如,中文中的“句子”的文法如下。
    〈句子〉::=〈主语〉〈谓语〉〈宾语〉
    〈主语〉::=〈代词〉|〈名词〉
    〈谓语〉::=〈动词〉
    〈宾语〉::=〈代词〉|〈名词〉
    〈代词〉你|我|他
    〈名词〉7大学生I筱霞I英语
    〈动词〉::=是|学习

    注:这里的符号“::=”表示“定义为”的意思,用“〈”和“〉”括住的是非终结符,没有括住的是终结符。

    2) 句子

      句子是语言的基本单位,是语言集中的一个元素,它由终结符构成,能由“文法”推导出。例如,上述文法可以推出“我是大学生”,所以它是句子。

    3) 语法树

      语法树是句子结构的一种树型表示,它代表了句子的推导结果,它有利于理解句子语法结构的层次。下图所示是“我是大学生”的语法树。
    句子“我是大学生”的语法树
      解释器模式的结构与组合模式相似,不过其包含的组成元素比组合模式多,而且组合模式是对象结构型模式,而解释器模式是类行为型模式。

    1. 解释器模式的结构

      解释器模式包含以下主要角色。

    • 抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法interpret()。
    • 终结符表达式(Terminal
      Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。
    • 非终结符表达式(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。
    • 环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
    • 客户端(Client):主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。

    解释器模式的结构图如图所示
    解释器模式的结构图

    2. 解释器模式的实现

      解释器模式的实现代码如下:

    package Interpreter;
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class InterpreterTest {
    
        public static void main(String[] args) {
            //定义环境对象
            Contexts contexts = new Contexts();
    
            //终结符表达式类
            Context x = new Context();
    
            contexts.put(x,10);
    
            NonterminalExpression nonterminalExpression = new NonterminalExpression(x);
    
            Integer result = nonterminalExpression.interpret(contexts);
    
            //得到结果:
            System.out.println("result=" + result);
    
        }
    
    }
    
    //抽象表达式类
    interface AbstractExpression {
        public Integer interpret(Contexts ctxs);    //解释方法
    }
    
    //终结符表达式类
    class Context implements AbstractExpression {
    
        @Override
        public Integer interpret(Contexts ctxs) {
            //对终结符表达式的处理
            return ctxs.get(this);
        }
    
    }
    
    //非终结符表达式类
    class NonterminalExpression implements AbstractExpression {
        AbstractExpression num;
    
        //传入要运算的符号(对象)
        public NonterminalExpression(AbstractExpression num) {
            this.num = num;
        }
    
        @Override
        public Integer interpret(Contexts ctxs) {
            //非对终结符表达式的处理  这里为非终结符表达式类对象的2次幂
            return num.interpret(ctxs) *  num.interpret(ctxs);
        }
    }
    
    //环境类
    class Contexts {
    
        Map<Context, Integer> map = new HashMap<>();
    
        public void put(Context ctx, int value) {
            map.put(ctx, value);
        }
    
        public int get(Context ctx) {
            return map.get(ctx);
        }
    
    }
    
    

    程序运行结果如下:

    result=100
    

    解释器模式应用场景

      前面介绍了解释器模式的结构与特点,下面分析它的应用场景。

    • 当语言的文法较为简单,且执行效率不是关键问题时。
    • 当问题重复出现,且可以用一种简单的语言来进行表达时。
    • 当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候,如 XML 文档解释。

      注意:解释器模式在实际的软件开发中使用比较少,因为它会引起效率、性能以及维护等问题。如果碰到对表达式的解释,在 Java 中可以用 Expression4J 或 Jep 等来设计

    解释器模式的扩展

      在项目开发中,如果要对数据表达式进行分析与计算,无须再用解释器模式进行设计了,Java 提供了以下强大的数学公式解析器:Expression4J、MESP(Math Expression String Parser) 和 Jep 等,它们可以解释一些复杂的文法,功能强大,使用简单。

    现在以 Jep 为例来介绍该工具包的使用方法。Jep 是 Java expression parser 的简称,即 Java 表达式分析器,它是一个用来转换和计算数学表达式的 Java 库。通过这个程序库,用户可以以字符串的形式输入一个任意的公式,然后快速地计算出其结果。而且 Jep 支持用户自定义变量、常量和函数,它包括许多常用的数学函数和常量。

    使用前先下载 Jep 压缩包,解压后,将 jep-x.x.x.jar 文件移到选择的目录中,在 Eclipse 的“Java 构建路径”对话框的“库”选项卡中选择“添加外部 JAR(X)…”,将该 Jep 包添加项目中后即可使用其中的类库。或者使用maven引入

      <dependencies>
            <dependency>
                <groupId>org.scijava</groupId>
                <artifactId>jep</artifactId>
                <version>2.4.1</version>
            </dependency>
        </dependencies>
    

    下面以计算存款利息为例来介绍。存款利息的计算公式是:本金x利率x时间=利息,其相关代码如下:

    //HookTemplateMethodTest.java
    package Interpreter;
    
    
    import org.nfunk.jep.JEP;
    
    public class JepTest {
    
        public static void main(String[] args) {
            JEP jep = new JEP();
            //定义要计算的数据表达式
            String 存款利息 = "本金*利率*时间";
            //给相关变量赋值
            jep.addVariable("本金", 10000);
            jep.addVariable("利率", 0.038);
            jep.addVariable("时间", 2);
            jep.parseExpression(存款利息);    //解析表达式
            Object accrual = jep.getValue();    //计算
            System.out.println("存款利息:" + accrual);
        }
    }
    

    程序运行结果如下:

    存款利息:760.0
    
    展开全文
  • Java设计模式之行为型:解释器模式

    千次阅读 2018-11-05 16:11:51
    解释器模式,就是定义语言的文法,并建立一个解释器来解释该语言中的句子,通过构建解释器,解决某一频繁发生的特定类型问题实例。
  • 设计模式之解释器模式

    千次阅读 2016-09-13 11:10:11
    解释器模式解释器模式是一种类行为型模式,它主要是用来解释特定语言的特定文法表示,虽然这个在实际生产中不常用到,但是我们学习一下还是有帮助的。
  • 设计模式-解释器模式

    千次阅读 2019-04-04 09:33:30
    给定一种语言,定义他的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中句子。 角色: AbstractExpression:抽象解释器 声明一个抽象的解释操作,这个接口为抽象语法树中所有的节点所共享。 ...
  • [设计模式]解释器模式

    千次阅读 2020-04-17 08:40:30
    点我进入github 如需了解更多设计模式,请进入我的设计模式专栏 解释器模式 提供如何定义语言的文法,以及对语言句子的解释方法 ...扩展性好,由于在解释器模式中使用类来表示语言的文法规则,因...
  • Android设计模式之——解释器模式

    千次阅读 2017-10-29 20:10:20
    在这么设计模式中,解释器模式在实际运用上相对来说要少很,因为我们很少会自己去构造一个语言的文法。虽然如此,既然它能够在设计模式中有一席之位,那么必定有它的可用之处。二、定义给定一个语言,定义它的...
  • 设计模式笔记--解释器模式

    千次阅读 2016-01-13 17:20:16
    设计模式笔记--解释器模式
  • C#设计模式之15-解释器模式

    万次阅读 2018-08-03 00:42:01
    解释器模式属于行为型模式,给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。 解释器模式提供了评估语言的语法或表达式的方式。这种模式实现了一个表达式接口,该接口...
  • 设计模式(20)解释器模式

    万次阅读 2019-05-02 12:38:14
    **定义:**给定一种语言,定义他的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中句子。 **类型:**行为类模式 类图: ​ 解释器模式是一个比较少用的模式,本人之前也没有用过这个模式。下面...
  • PHP设计模式——解释器模式

    千次阅读 2015-05-31 21:23:59
    声明:本系列博客参考... 解释器模式:Given a language, define arepresentation for its grammar along with an interpreter that uses therepresentation to interpret sentences in the language。给定一个语言
  • 在现实的开发中,这些简单的自定义语言可以通过现有的编程语言设计,如果所基于的编程语言是面向对象语言,此时可以使用解释器模式来实现自定义语言 什么是解释器模式 Interpreter Pattern 定义一个语言的方法,...
  • 设计模式之解释器模式(行为型)

    千次阅读 2019-04-15 21:33:57
    解释器模式(Interpreter Pattern):定义语言的文法,并且建立一个解释器来解释改语言中的句子,这里的“语言”意思是规定格式和语法的代码,所以解释器模式是一种类行为型模式 二、模式角色 Context: 环境类 Client...
  • 本文讲的是3个步骤实现简单语言解释器(自制简易编程语言), 前段时间,我用javascript重新编写了一个16位的虚拟机,用它实现了一个定制的CPU架构和汇编语言,汇编器和调试器。虽然从头编一个语言可以完全实现自己...
  • 给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
  •  解释器模式是一种使用频率相对较低但学习难度较大的设计模式,它用于描述如何使用面向对象语言构成一个简单的语言解释器。在某些情况下,为了更好地描述某一些特定类型的问题,我们可以创建一种新的语言,这种语言...
  • 1. 前文汇总 ...常见的解释器有 JVM 为我们提供的 Java 语言解释器,还有我们经常使用的 MySQL ,也有内置的 SQL 解释器。 不过没用是没用,对应的模式我们还是可以学习一下。 2.1 定义 解释器模式(Interp.
  • 设计模式-行为型- 解释器模式(Interpreter)
  • 自定义语言的实现——解释器模式(六)

    千次阅读 热门讨论 2012-07-04 00:23:37
     解释器模式为自定义语言设计和实现提供了一种解决方案,它用于定义一组文法规则并通过这组文法规则来解释语言中的句子。虽然解释器模式的使用频率不是特别高,但是它在正则表达式、XML文档解释等领域还是得到了...
  • 深入浅出基于Java的解释器设计模式

    千次阅读 2005-01-10 17:54:00
    一、引子 其实没有什么好的例子引入解释器模式,因为它描述了如何构成一个简单的语言解释器,主要应用在使用面向对象语言开发编译器中;在实际应用中,我们可能很少碰到去构造一个语言的文法的情况。 虽然你几乎用...
  • [设计模式]Interpreter解释器模式

    千次阅读 2013-01-11 18:41:37
    通常当一个语言需要解释执行,并且你可以将该语言的句子表示成为一个抽象的语法树时,可以使用解释器模式 解析 1)context:包含解释器之外的一些全局信息 2)AbstractExpression:抽象基类,声明一个抽象的...
  • 【JS设计模式】解释器模式代码示例

    千次阅读 2016-06-13 14:08:13
    Interpreter是行为模式之一,它是一种特殊的设计模式,它建立一个解释器,对于特定的计算机程序设计语言,用来解释预先定义的文法。 2、模式结构图 模式所涉及的角色如下所示:  (1)抽象表达式(Expression)角色...
  • C#设计模式之16——解释器模式

    千次阅读 2012-03-02 13:55:47
    C#设计模式之16——解释器模式 有些程序受益于以一种语言来描述其能够执行的操作。解释器模式描述的内容通常包括:定义该种语言的语法,并使用这一语法来解释以该种语言做陈述的语句。 当某个程序要处理多种不同...
  • 定义一个语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。(Given a language, define are presentation for its grammar along with an interpreter that uses there ...
  • 23种设计模式(14):解释器模式

    万次阅读 多人点赞 2012-06-15 12:34:25
    定义:给定一种语言,定义他的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中句子。 类型:行为类模式 类图:  解释器模式是一个比较少用的模式,本人之前也没有用过这个模式。下面我们...
  • 这篇博客我们来介绍一下解释器模式...在这么设计模式中,解释器模式在实际运用上相对来说要少很,因为我们很少会去构造一个语言的文法。虽然你几乎用不到这个模式,但是看一看还是能受到一定的启发的。  转载请
  • 设计模式C++实现-解释器模式

    千次阅读 2015-06-07 11:22:49
    解释器模式(interpreter):给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。   说明 包含哪些角色? (1)AbstractExpression(抽象表达式):声明一个抽象的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 253,764
精华内容 101,505
关键字:

多语言解释器设计