【设计模式——学习笔记】23种设计模式——解释器模式Interpreter(原理讲解+应用场景介绍+案例介绍+Java代码实现)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 【设计模式——学习笔记】23种设计模式——解释器模式Interpreter(原理讲解+应用场景介绍+案例介绍+Java代码实现)

案例引入

通过解释器模式来实现四则运算,如计算a+b-c的值,具体要求

  • 先输入表达式的形式,比如a+b+c-d+e,要求表达式的字母不能重复
  • 在分别输入a,b,c,d,e的值
  • 最后求出结果

传统方案

  • 编写一个方法,接收表达式的形式,然后根据用户输入的数值进行解析,得到结果

【分析】

如果加入新的运算符,比如*或/等等,不利于扩展,另外让一个方法来解析会造成程序结构混乱,不够清晰

【改进】

可以考虑使用解释器模式,即表达式->解释器(可以有多种解释器)->结果

介绍

基本介绍

  • 在解释器模式中,程序要解决的问题会被用非常简单的“迷你语言”表述出来,即用“迷你语言”编写的迷你程序把具体的问题表述出来。迷你程序是无法单独工作的,我们还需要用Java语言编写一个负责“翻译”(interpreter)的程序。翻译程序会理解迷你语言并解释迷你语言,最后运行迷你程序。这段翻译程序也被称为解释器。这样,当需要解决的问题发生变化时,不需要修改 Java语言程序,只需要修改迷你语言程序即可应对
  • 在编译原理中,一个算术表达式通过词法分析器形成词法单元,然后这些词法单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树。这里的词法分析器和语法分析器都可以看做是解释器

应用场景

  • 可以将一个需要解释执行的语言中的句子表示为一个抽象语法树,一些重复出现的问题可以用一种简单的语言来表达,比如下列场景:编译器、运算表达式计算、正则表达式、机器人指令……

登场角色

  • AbstractExpression(抽象表达式):抽象表达式,声明一个抽象的解释操作(定义了语法树节点的共同接口),这个方法为抽象语法树中所有的节点所共享,方法可以取名为parse/interpreter,译为解析/翻译
  • TerminalExpression(终结符表达式):为终结符表达式,实现与文法中的终结符相关的解释操作
  • NonTermialExpression(非终结符表达式):为非终结符表达式,为文法中的非终结符实现解释操作
  • Context(上下文):是环境角色,含有解释器之外的全局信息,为解释器进行语法解析提供了必要的信息
  • Client(请求者):调用TerminalExpression和NonTermialExpression来推导语法树

案例实现

案例一

类图

实现

【Expression】

package com.atguigu.interpreter;
import java.util.HashMap;
/**
 * 抽象类表达式,通过HashMap键值对, 可以获取到变量的值
 *
 * @author Administrator
 *
 */
public abstract class Expression {
   /**
    * 如表达式是:a + b - c ,key就是公式(表达式)的参数a、b、c, value就是就是具体值
    * 实例:HashMap {a=10, b=20}
    * @param var
    * @return
    */
   public abstract int interpreter(HashMap<String, Integer> var);
}

【变量解析器】

package com.atguigu.interpreter;
import java.util.HashMap;
/**
 * 变量的解释器
 * @author Administrator
 *
 */
public class VarExpression extends Expression {
   /**
    * key=a,key=b,key=c
    */
   private String key;
   public VarExpression(String key) {
      this.key = key;
   }
   /**
    * var 就是{a=10, b=20}
    * interpreter的功能就是根据变量名称来返回对应值
    * @param var
    * @return
    */
   @Override
   public int interpreter(HashMap<String, Integer> var) {
      return var.get(this.key);
   }
}

【抽象的运算符号解释器】

package com.atguigu.interpreter;
import java.util.HashMap;
/**
 * 抽象运算符号解析器
 * 每个运算符号,都只和自己左右两个数字有关系,
 * 但左右两个数字有可能也是一个解析的结果,无论何种类型,都是Expression类的实现类
 *
 * @author Administrator
 *
 */
public class SymbolExpression extends Expression {
   protected Expression left;
   protected Expression right;
   public SymbolExpression(Expression left, Expression right) {
      this.left = left;
      this.right = right;
   }
   /**
    * 因为 SymbolExpression 是让其子类来实现,因此 interpreter 是一个默认实现
    * @param var
    * @return
    */
   @Override
   public int interpreter(HashMap<String, Integer> var) {
      // 默认实现
      return 0;
   }
}

【具体的运算符号解释器:加法解释器】

package com.atguigu.interpreter;
import java.util.HashMap;
/**
 * 加法解释器
 * @author Administrator
 *
 */
public class AddExpression extends SymbolExpression  {
   public AddExpression(Expression left, Expression right) {
      super(left, right);
   }
   /**
    * 处理相加
    * var 仍然是 {a=10,b=20}..
    * @param var
    * @return
    */
   public int interpreter(HashMap<String, Integer> var) {
      // super.left.interpreter(var):返回 left 表达式对应的值 a = 10
      // super.right.interpreter(var): 返回 right 表达式对应值 b = 20
      // 将运算左表达式的值和右表达式相加
      return super.left.interpreter(var) + super.right.interpreter(var);
   }
}

【具体的运算符号解释器:减法解释器】

package com.atguigu.interpreter;
import java.util.HashMap;
/**
 * 减法解释器
 */
public class SubExpression extends SymbolExpression {
    public SubExpression(Expression left, Expression right) {
        super(left, right);
    }
    /**
     * 求出left 和 right 表达式相减后的结果
     *
     * @param var
     * @return
     */
    public int interpreter(HashMap<String, Integer> var) {
        return super.left.interpreter(var) - super.right.interpreter(var);
    }
}

【计算器】

package com.atguigu.interpreter;
import java.util.HashMap;
import java.util.Stack;
public class Calculator {
   /**
    * 定义表达式
    */
   private Expression expression;
   /**
    * 构造函数传参,解析字符串生成表达式
    * @param expStr
    */
   public Calculator(String expStr) {
      // 如 expStr = a+b
      // 安排运算先后顺序
      Stack<Expression> stack = new Stack<>();
      // 表达式拆分成字符数组,变成[a, +, b]
      char[] charArray = expStr.toCharArray();
      Expression left = null;
      Expression right = null;
      //遍历我们的字符数组, 即遍历  [a, +, b]
      //针对不同的情况,做处理
      for (int i = 0; i < charArray.length; i++) {
         switch (charArray[i]) {
            case '+':
               // 从stack取出左表达式 "a"
               left = stack.pop();
               // 取出右表达式 "b"
               right = new VarExpression(String.valueOf(charArray[++i]));
               // 然后根据得到left和right构建AddExpresson加入stack
               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:
               //如果是一个 Var 就创建要给 VarExpression 对象,并push到stack
               stack.push(new VarExpression(String.valueOf(charArray[i])));
               break;
         }
      }
      //当遍历完整个charArray数组后,stack就得到最终的Expression
      this.expression = stack.pop();
   }
   public int run(HashMap<String, Integer> var) {
      //最后将表达式 a+b 和 var={a=10,b=20}
      //然后传递给expression的interpreter进行解释执行
      return this.expression.interpreter(var);
   }
}

【客户端】

package com.atguigu.interpreter;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
public class ClientTest {
    public static void main(String[] args) throws IOException {
        // a+b
        String expStr = getExpStr();
        // var {a=10, b=20}
        HashMap<String, Integer> var = getValue(expStr);
        Calculator calculator = new Calculator(expStr);
        System.out.println("运算结果:" + expStr + "=" + calculator.run(var));
    }
    /**
     * 获得表达式
     *
     * @return
     * @throws IOException
     */
    public static String getExpStr() throws IOException {
        System.out.print("请输入表达式:");
        return (new BufferedReader(new InputStreamReader(System.in))).readLine();
    }
    /**
     * 获得值映射
     *
     * @param expStr
     * @return
     * @throws IOException
     */
    public static HashMap<String, Integer> getValue(String expStr) throws IOException {
        HashMap<String, Integer> map = new HashMap<>();
        for (char ch : expStr.toCharArray()) {
            if (ch != '+' && ch != '-') {
                if (!map.containsKey(String.valueOf(ch))) {
                    System.out.print("请输入" + String.valueOf(ch) + "的值:");
                    String in = (new BufferedReader(new InputStreamReader(System.in))).readLine();
                    map.put(String.valueOf(ch), Integer.valueOf(in));
                }
            }
        }
        return map;
    }
}

【运行】

Connected to the target VM, address: '127.0.0.1:4322', transport: 'socket'
请输入表达式:a+b
请输入a的值:10
请输入b的值:20
运算结果:a+b=30
Disconnected from the target VM, address: '127.0.0.1:4322', transport: 'socket'
Process finished with exit code 0

【执行过程】

  • 第一次循环:将变量解析器a放入到栈中
  • 第二次循环: 从stastack取出左表达式"a",接着中数组中获取并生成新的表达式"b",最后构建加法表达式"a+b"存储到栈中

案例二

说明

有一辆小车,需要编写一个简单的小程序控制小车的移动,比如program go right go right go right go right end,小车收到指令之后,就会走出如下的轨迹

类图

实现

【抽象表达式:Node】

package com.atguigu.interpreter.Sample;
/**
 * 语法树中各个部分(节点)中最顶层的类
 */
public abstract class Node {
    /**
     * 进行语法解析处理
     *
     * @param context 语法解析上下文的类
     * @throws ParseException
     */
    public abstract void parse(Context context) throws ParseException;
}

【自定义的 解析异常】

package com.atguigu.interpreter.Sample;
public class ParseException extends Exception {
    public ParseException(String msg) {
        super(msg);
    }
}

【终结符表达式:PrimitiveCommandNode】

终结符表达式:不会进一步展开继续调用parse方法

package com.atguigu.interpreter.Sample;
// <primitive command> ::= go | right | left
public class PrimitiveCommandNode extends Node {
    /**
     * 记录 指令的名字 如 go left right
     */
    private String name;
    /**
     * PrimitiveCommandNode 的 parse 方法没有调用其他类的parse方法
     * @param context 语法解析上下文的类
     * @throws ParseException
     */
    public void parse(Context context) throws ParseException {
        // 记录指令的名字
        name = context.currentToken();
        context.skipToken(name);
        if (!name.equals("go") && !name.equals("right") && !name.equals("left")) {
            throw new ParseException(name + " is undefined");
        }
    }
    public String toString() {
        return name;
    }
}

【非终结符表达式:ProgramNode】

package com.atguigu.interpreter.Sample;
// <program> ::= program <command list>
public class ProgramNode extends Node {
    private Node commandListNode;
    public void parse(Context context) throws ParseException {
        // 迷你语法最开始会出现单词program,这行代码可以跳过 program 这个标记
        // 比如一开始context的值是program end,那么currentToken的值就是program,执行context.skipToken("program")后,currentToken的值变成end
        context.skipToken("program");
        commandListNode = new CommandListNode();
        commandListNode.parse(context);
    }
    public String toString() {
        // 等效于  return "[program " + commandListNode.toString() + "]";
        return "[program " + commandListNode + "]";
    }
}

【非终结符表达式:ProgramNode】

package com.atguigu.interpreter.Sample;
// <program> ::= program <command list>
public class ProgramNode extends Node {
    private Node commandListNode;
    public void parse(Context context) throws ParseException {
        // 迷你语法最开始会出现单词program,这行代码可以跳过 program 这个标记
        // 比如一开始context的值是program end,那么currentToken的值就是program,执行context.skipToken("program")后,currentToken的值变成end
        context.skipToken("program");
        commandListNode = new CommandListNode();
        commandListNode.parse(context);
    }
    public String toString() {
        // 等效于  return "[program " + commandListNode.toString() + "]";
        return "[program " + commandListNode + "]";
    }
}

【非终结符表达式:CommandNode】

package com.atguigu.interpreter.Sample;
// <command> ::= <repeat command> | <primitive command>
public class CommandNode extends Node {
    private Node node;
    public void parse(Context context) throws ParseException {
        if (context.currentToken().equals("repeat")) {
            // 使用repeat解析器
            node = new RepeatCommandNode();
            node.parse(context);
        } else {
            // 使用指令解释器,解析go left right等指令
            node = new PrimitiveCommandNode();
            node.parse(context);
        }
    }
    public String toString() {
        return node.toString();
    }
}

【非终结符表达式:CommandListNode】

package com.atguigu.interpreter.Sample;
import java.util.ArrayList;
// <command list> ::= <command>* end
public class CommandListNode extends Node {
    /**
     * 保存多个命令
     */
    private ArrayList list = new ArrayList();
    public void parse(Context context) throws ParseException {
        while (true) {
            if (context.currentToken() == null) {
                // 如果context.currentToken() == null,表示后面没有任何标记了(即已经解析到迷你程序的末尾),说明缺少了end,抛出异常
                throw new ParseException("Missing 'end'");
            } else if (context.currentToken().equals("end")) {
                // 如果当前的标记是end,表示已经解析至末尾,end不需要执行,直接跳过即可
                context.skipToken("end");
                // 到了end,解析已经完成了,退出循环即可
                break;
            } else {
                // 当前标记不是end,则是其他需要解析的标记
                Node commandNode = new CommandNode();
                // 解析标记
                commandNode.parse(context);
                list.add(commandNode);
            }
        }
    }
    public String toString() {
        return list.toString();
    }
}

【非终结符表达式:RepeatCommandNode】

package com.atguigu.interpreter.Sample;
// <repeat command> ::= repeat <number> <command list>
public class RepeatCommandNode extends Node {
    /**
     * 循环调用的次数
     */
    private int number;
    private Node commandListNode;
    public void parse(Context context) throws ParseException {
        context.skipToken("repeat");
        number = context.currentNumber();
        context.nextToken();
        commandListNode = new CommandListNode();
        commandListNode.parse(context);
    }
    public String toString() {
        return "[repeat " + number + " " + commandListNode + "]";
    }
}

【Context】

package com.atguigu.interpreter.Sample;
import java.util.StringTokenizer;
/**
 * 该类提供语法解析需要的方法
 */
public class Context {
    /**
     * 使用java.util.stringTokenizer类来简化程序,它会将接收到的字符串分割为标记。
     * 在分割字符串时使用的分隔符是空格“”、制表符“\t”、换行符“\n”回车符“\r”、换页符“\f”
     */
    private StringTokenizer tokenizer;
    private String currentToken;
    public Context(String text) {
        tokenizer = new StringTokenizer(text);
        nextToken();
    }
    /**
     * 获取下一个标记
     *
     * @return
     */
    public String nextToken() {
        // 当判断还有下一个标记时,就获取下一个标记
        if (tokenizer.hasMoreTokens()) {
            currentToken = tokenizer.nextToken();
        } else {
            currentToken = null;
        }
        return currentToken;
    }
    /**
     * 返回当前的标记
     * @return
     */
    public String currentToken() {
        return currentToken;
    }
    /**
     * 跳过标记
     *
     * @param token
     * @throws ParseException
     */
    public void skipToken(String token) throws ParseException {
        if (!token.equals(currentToken)) {
            throw new ParseException("Warning: " + token + " is expected, but " + currentToken + " is found.");
        }
        nextToken();
    }
    /**
     * 读取数字
     *
     * @return
     * @throws ParseException
     */
    public int currentNumber() throws ParseException {
        int number = 0;
        try {
            number = Integer.parseInt(currentToken);
        } catch (NumberFormatException e) {
            throw new ParseException("Warning: " + e);
        }
        return number;
    }
}

【Client:Main】

package com.atguigu.interpreter.Sample;
import java.io.BufferedReader;
import java.io.FileReader;
public class Main {
    public static void main(String[] args) {
        try {
            BufferedReader reader = new BufferedReader(new FileReader("src/com/atguigu/interpreter/Sample/program.txt"));
            String text;
            while ((text = reader.readLine()) != null) {
                System.out.println("迷你程序 = \"" + text + "\"");
                Node node = new ProgramNode();
                node.parse(new Context(text));
                System.out.println("语法解析结果 = " + node);
                System.out.println();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

【program.txt】

program end
program go end
program go right go right go right go right end
program repeat 4 go right end end
program repeat 4 repeat 3 go right go left end right end end

【运行】

迷你程序 = "program end"
语法解析结果 = [program []]
迷你程序 = "program go end"
语法解析结果 = [program [go]]
迷你程序 = "program go right go right go right go right end"
语法解析结果 = [program [go, right, go, right, go, right, go, right]]
迷你程序 = "program repeat 4 go right end end"
语法解析结果 = [program [[repeat 4 [go, right]]]]
迷你程序 = "program repeat 4 repeat 3 go right go left end right end end"
语法解析结果 = [program [[repeat 4 [[repeat 3 [go, right, go, left]], right]]]]
Process finished with exit code 0

拓展

  • 上面的程序的功能只是将迷你程序解析出来,并没有真正执行其中的指令,下面将继续完善这个程序,让小车可以真正根据指令执行起来
  • 下面的代码属实有点绕,代码不只是使用了解释器模式,还使用了外观模式来让解释器更加便于使用,除此之外,还使用工厂方法模式来提供createExecutor(String name)方法来根据指令名称生成相应的执行器,请大伙们慢慢欣赏

【ParseException】

package com.atguigu.interpreter.A1.language;
public class ParseException extends Exception {
    public ParseException(String msg) {
        super(msg);
    }
}

【ExecuteException】

package com.atguigu.interpreter.A1.language;
public class ExecuteException extends Exception {
    public ExecuteException(String msg) {
        super(msg);
    }
}

【Node】

package com.atguigu.interpreter.A1.language;
/**
 * 实现Executor执行器接口
 */
public abstract class Node implements Executor {
    public abstract void parse(Context context) throws ParseException;
}

【ProgramNode】

package com.atguigu.interpreter.A1.language;
public class ProgramNode extends Node {
    private Node commandListNode;
    public void parse(Context context) throws ParseException {
        context.skipToken("program");
        commandListNode = new CommandListNode();
        commandListNode.parse(context);
    }
    public void execute() throws ExecuteException {
        // 连续执行多个指令 的 execute方法
        commandListNode.execute();
    }
    public String toString() {
        return "[program " + commandListNode + "]";
    }
}

【CommandNode】

package com.atguigu.interpreter.A1.language;
public class CommandNode extends Node {
    private Node node;
    public void parse(Context context) throws ParseException {
        if (context.currentToken().equals("repeat")) {
            node = new RepeatCommandNode();
            node.parse(context);
        } else {
            node = new PrimitiveCommandNode();
            node.parse(context);
        }
    }
    /**
     * 直接调用 RepeatCommandNode 和 PrimitiveCommandNode 的执行器
     * @throws ExecuteException
     */
    public void execute() throws ExecuteException {
        node.execute();
    }
    public String toString() {
        return node.toString();
    }
}

【CommandListNode】

package com.atguigu.interpreter.A1.language;
import java.util.ArrayList;
import java.util.Iterator;
public class CommandListNode extends Node {
    private ArrayList list = new ArrayList();
    public void parse(Context context) throws ParseException {
        while (true) {
            if (context.currentToken() == null) {
                throw new ParseException("Missing 'end'");
            } else if (context.currentToken().equals("end")) {
                context.skipToken("end");
                break;
            } else {
                Node commandNode = new CommandNode();
                commandNode.parse(context);
                list.add(commandNode);
            }
        }
    }
    /**
     * 使用迭代器来自动执行指令
     *
     * @throws ExecuteException
     */
    public void execute() throws ExecuteException {
        Iterator it = list.iterator();
        while (it.hasNext()) {
            ((CommandNode) it.next()).execute();
        }
    }
    public String toString() {
        return list.toString();
    }
}

【PrimitiveCommandNode】

package com.atguigu.interpreter.A1.language;
public class PrimitiveCommandNode extends Node {
    private String name;
    private Executor executor;
    public void parse(Context context) throws ParseException {
        name = context.currentToken();
        context.skipToken(name);
        // 根据指令名称来找工厂获取相应的执行器
        executor = context.createExecutor(name);
    }
    public void execute() throws ExecuteException {
        if (executor == null) {
            throw new ExecuteException(name + ": is not defined");
        } else {
            executor.execute();
        }
    }
    public String toString() {
        return name;
    }
}

【RepeatCommandNode】

package com.atguigu.interpreter.A1.language;
public class RepeatCommandNode extends Node {
    private int number;
    private Node commandListNode;
    public void parse(Context context) throws ParseException {
        context.skipToken("repeat");
        number = context.currentNumber();
        context.nextToken();
        commandListNode = new CommandListNode();
        commandListNode.parse(context);
    }
    public void execute() throws ExecuteException {
        // 循环执行指令
        for (int i = 0; i < number; i++) {
            commandListNode.execute();
        }
    }
    public String toString() {
        return "[repeat " + number + " " + commandListNode + "]";
    }
}

【Context】

package com.atguigu.interpreter.A1.language;
import java.util.StringTokenizer;
public class Context implements ExecutorFactory {
    /**
     * 组合工厂类
     */
    private ExecutorFactory factory;
    private StringTokenizer tokenizer;
    private String currentToken;
    public Context(String text) {
        tokenizer = new StringTokenizer(text);
        nextToken();
    }
    public String nextToken() {
        if (tokenizer.hasMoreTokens()) {
            currentToken = tokenizer.nextToken();
        } else {
            currentToken = null;
        }
        return currentToken;
    }
    public String currentToken() {
        return currentToken;
    }
    public void skipToken(String token) throws ParseException {
        if (!token.equals(currentToken)) {
            throw new ParseException("Warning: " + token + " is expected, but " + currentToken + " is found.");
        }
        nextToken();
    }
    public int currentNumber() throws ParseException {
        int number = 0;
        try {
            number = Integer.parseInt(currentToken);
        } catch (NumberFormatException e) {
            throw new ParseException("Warning: " + e);
        }
        return number;
    }
    /**
     * 设置工厂
     * @param factory
     */
    public void setExecutorFactory(ExecutorFactory factory) {
        this.factory = factory;
    }
    /**
     * 使用工厂的方法来创建具体的执行器
     * @param name
     * @return
     */
    public Executor createExecutor(String name) {
        // 后面的终结符
        return factory.createExecutor(name);
    }
}

【Executor】

package com.atguigu.interpreter.A1.language;
/**
 * 外观对象的窗口接口
 */
public interface Executor {
    /**
     * 向系统外部提供一个接口
     * @throws ExecuteException
     */
    public abstract void execute() throws ExecuteException;
}

【InterpreterFacade】

package com.atguigu.interpreter.A1.language;
public class InterpreterFacade implements Executor {
    private ExecutorFactory factory;
    private Context context;
    private Node programNode;
    public InterpreterFacade(ExecutorFactory factory) {
        this.factory = factory;
    }
    /**
     * 提供给外层访问的解析接口
     * @param text
     * @return
     */
    public boolean parse(String text) {
        boolean ok = true;
        this.context = new Context(text);
        this.context.setExecutorFactory(factory);
        this.programNode = new ProgramNode();
        try {
            // 开始解析
            programNode.parse(context);
            System.out.println(programNode.toString());
        } catch (ParseException e) {
            e.printStackTrace();
            ok = false;
        }
        return ok;
    }
    public void execute() throws ExecuteException {
        try {
            // 开始执行程序
            programNode.execute();
        } catch (ExecuteException e) {
            e.printStackTrace();
        }
    }
}

【ExecutorFactory】

package com.atguigu.interpreter.A1.language;
public interface ExecutorFactory {
    /**
     * 创建一个执行器
     * @param name
     * @return
     */
    public abstract Executor createExecutor(String name);
}

【TurtleCanvas】

package com.atguigu.interpreter.A1.turtle;
import com.atguigu.interpreter.A1.language.ExecuteException;
import com.atguigu.interpreter.A1.language.Executor;
import com.atguigu.interpreter.A1.language.ExecutorFactory;
import java.awt.*;
public class TurtleCanvas extends Canvas implements ExecutorFactory {
    /**
     * 前进时的长度单位
     */
    final static int UNIT_LENGTH = 30;
    /**
     * 上方
     */
    final static int DIRECTION_UP = 0;
    /**
     * 右方
     */
    final static int DIRECTION_RIGHT = 3;
    /**
     * 下方
     */
    final static int DIRECTION_DOWN = 6;
    /**
     * 左方
     */
    final static int DIRECTION_LEFT = 9;
    /**
     * 右转
     */
    final static int RELATIVE_DIRECTION_RIGHT = 3;
    /**
     * 左转
     */
    final static int RELATIVE_DIRECTION_LEFT = -3;
    /**
     * 半径
     */
    final static int RADIUS = 3;
    /**
     * 移动方向
     */
    private int direction = 0;
    /**
     * 小车的定位
     */
    private Point position;
    private Executor executor;
    public TurtleCanvas(int width, int height) {
        // 设置画布尺寸
        setSize(width, height);
        initialize();
    }
    public void setExecutor(Executor executor) {
        this.executor = executor;
    }
    /**
     * 修改小车的行驶方向
     *
     * @param relativeDirection
     */
    void setRelativeDirection(int relativeDirection) {
        setDirection(direction + relativeDirection);
    }
    void setDirection(int direction) {
        if (direction < 0) {
            direction = 12 - (-direction) % 12;
        } else {
            direction = direction % 12;
        }
        this.direction = direction % 12;
    }
    /**
     * 让小车移动
     *
     * @param length
     */
    void go(int length) {
        int newx = position.x;
        int newy = position.y;
        switch (direction) {
            case DIRECTION_UP:
                newy -= length;
                break;
            case DIRECTION_RIGHT:
                newx += length;
                break;
            case DIRECTION_DOWN:
                newy += length;
                break;
            case DIRECTION_LEFT:
                newx -= length;
                break;
            default:
                break;
        }
        Graphics g = getGraphics();
        if (g != null) {
            g.drawLine(position.x, position.y, newx, newy);
            g.fillOval(newx - RADIUS, newy - RADIUS, RADIUS * 2 + 1, RADIUS * 2 + 1);
        }
        position.x = newx;
        position.y = newy;
    }
    /**
     * 使用工厂模式根据指令名称创建一个对应的执行器,并将其赋值给Executor
     *
     * @param name
     * @return
     */
    public Executor createExecutor(String name) {
        if (name.equals("go")) {
            return new GoExecutor(this);
        } else if (name.equals("right")) {
            return new DirectionExecutor(this, RELATIVE_DIRECTION_RIGHT);
        } else if (name.equals("left")) {
            return new DirectionExecutor(this, RELATIVE_DIRECTION_LEFT);
        } else {
            return null;
        }
    }
    /**
     * 初始化
     */
    public void initialize() {
        Dimension size = getSize();
        // 将小车的初始位置放在画布的中心
        position = new Point(size.width / 2, size.height / 2);
        direction = 0;
        // 设置路径的颜色
        setForeground(Color.red);
        // 设置画布的背景颜色
        setBackground(Color.white);
        Graphics g = getGraphics();
        if (g != null) {
            // 清空画布
            g.clearRect(0, 0, size.width, size.height);
        }
    }
    /**
     * 绘制图像
     *
     * @param g the specified Graphics context
     */
    public void paint(Graphics g) {
        initialize();
        if (executor != null) {
            try {
                // 执行 执行器的方法 控制小车运动
                executor.execute();
            } catch (ExecuteException e) {
            }
        }
    }
}
abstract class TurtleExecutor implements Executor {
    protected TurtleCanvas canvas;
    public TurtleExecutor(TurtleCanvas canvas) {
        this.canvas = canvas;
    }
    public abstract void execute();
}
/**
 * 具体执行器:前进
 */
class GoExecutor extends TurtleExecutor {
    public GoExecutor(TurtleCanvas canvas) {
        super(canvas);
    }
    public void execute() {
        // 调用前进方法在画布中绘制小车的前进路径
        canvas.go(TurtleCanvas.UNIT_LENGTH);
    }
}
/**
 * 具体执行器:切换方向
 */
class DirectionExecutor extends TurtleExecutor {
    private int relativeDirection;
    public DirectionExecutor(TurtleCanvas canvas, int relativeDirection) {
        super(canvas);
        this.relativeDirection = relativeDirection;
    }
    public void execute() {
        // 修改小车的方向
        canvas.setRelativeDirection(relativeDirection);
    }
}

【Main】

package com.atguigu.interpreter.A1;
import com.atguigu.interpreter.A1.language.InterpreterFacade;
import com.atguigu.interpreter.A1.turtle.TurtleCanvas;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class Main extends Frame implements ActionListener {
    private TurtleCanvas canvas = new TurtleCanvas(400, 400);
    private InterpreterFacade facade = new InterpreterFacade(canvas);
    /**
     * 默认的迷你程序
     */
    private TextField programTextField = new TextField("program repeat 3 go right go left end end");
    /**
     * 构造函数
     *
     * @param title
     */
    public Main(String title) {
        super(title);
        canvas.setExecutor(facade);
        setLayout(new BorderLayout());
        programTextField.addActionListener(this);
        this.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
        // 将文本输入框添加到布局的上部分
        add(programTextField, BorderLayout.NORTH);
        // 将画布放在布局的中心
        add(canvas, BorderLayout.CENTER);
        pack();
        parseAndExecute();
        show();
    }
    /**
     * 供ActionListener用,监听用户的输入,当用户输入完成并按下回车之后,方法被执行
     *
     * @param e
     */
    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == programTextField) {
            parseAndExecute();
        }
    }
    /**
     * 解析迷你程序成指令,并执行指令
     */
    private void parseAndExecute() {
        // 获取用户输入的迷你程序
        String programText = programTextField.getText();
        System.out.println("programText = " + programText);
        // 直接调用外观对象所提供的上层接口来使用解释器模式来解析迷你程序
        facade.parse(programText);
        // 重新绘制结果
        canvas.repaint();
    }
    public static void main(String[] args) {
        new Main("Interpreter Pattern Sample");
    }
}

【运行】

解释器模式在Spring框架中的应用

package com.atguigu.spring.test;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
public class Interpreter {
   public static void main(String[] args) {
      //创建一个 Parser 对象
      SpelExpressionParser parser = new SpelExpressionParser();
      //通过 Parser 对象 获取到一个Expression对象
      //会根据不同的 Parser 对象 ,返回不同的 Expression 对象
      Expression expression = parser.parseExpression("10 * (2 + 1) * 1 + 66"); //结果:96
      int result = (Integer) expression.getValue();
      System.out.println(result);
   }
}

Expression子类

【说明】

  • Expression接口是表达式接口,其下面有不同的实现类,比如SpelExpression或者CompositeStringExpression
  • 使用的时候,根据你创建的不同的Parser对象,返回不同的Expression对象

  • 最后使用得到的Expression对象,调用其getValue解释执行表达式,来得到结果

总结

【优点】

  • 当有一个语言需要解释执行,可将该语言中的句子表示为一个抽象语法树,就可以考虑使用解释器模式,让程序具有良好的扩展性

【缺点】

  • 解释器模式会引起类膨胀、解释器模式采用递归调用方法,将会导致调试非常复杂、效率可能降低

文章说明

  • 本文章为本人学习尚硅谷的学习笔记,文章中大部分内容来源于尚硅谷视频(点击学习尚硅谷相关课程),也有部分内容来自于自己的思考,发布文章是想帮助其他学习的人更方便地整理自己的笔记或者直接通过文章学习相关知识,如有侵权请联系删除,最后对尚硅谷的优质课程表示感谢。
  • 本人还同步阅读《图解设计模式》书籍(图解设计模式/(日)结城浩著;杨文轩译–北京:人民邮电出版社,2017.1),进而综合两者的内容,让知识点更加全面
目录
相关文章
|
5天前
|
Java
在 Java 中捕获和处理自定义异常的代码示例
本文提供了一个 Java 代码示例,展示了如何捕获和处理自定义异常。通过创建自定义异常类并使用 try-catch 语句,可以更灵活地处理程序中的错误情况。
|
19天前
|
XML 安全 Java
Java反射机制:解锁代码的无限可能
Java 反射(Reflection)是Java 的特征之一,它允许程序在运行时动态地访问和操作类的信息,包括类的属性、方法和构造函数。 反射机制能够使程序具备更大的灵活性和扩展性
33 5
Java反射机制:解锁代码的无限可能
|
15天前
|
jenkins Java 测试技术
如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例详细说明
本文介绍了如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例,详细说明了从 Jenkins 安装配置到自动构建、测试和部署的全流程。文中还提供了一个 Jenkinsfile 示例,并分享了实践经验,强调了版本控制、自动化测试等关键点的重要性。
48 3
|
21天前
|
存储 安全 Java
系统安全架构的深度解析与实践:Java代码实现
【11月更文挑战第1天】系统安全架构是保护信息系统免受各种威胁和攻击的关键。作为系统架构师,设计一套完善的系统安全架构不仅需要对各种安全威胁有深入理解,还需要熟练掌握各种安全技术和工具。
59 10
|
16天前
|
分布式计算 Java MaxCompute
ODPS MR节点跑graph连通分量计算代码报错java heap space如何解决
任务启动命令:jar -resources odps-graph-connect-family-2.0-SNAPSHOT.jar -classpath ./odps-graph-connect-family-2.0-SNAPSHOT.jar ConnectFamily 若是设置参数该如何设置
|
15天前
|
Java
Java代码解释++i和i++的五个主要区别
本文介绍了前缀递增(++i)和后缀递增(i++)的区别。两者在独立语句中无差异,但在赋值表达式中,i++ 返回原值,++i 返回新值;在复杂表达式中计算顺序不同;在循环中虽结果相同但使用方式有别。最后通过 `Counter` 类模拟了两者的内部实现原理。
Java代码解释++i和i++的五个主要区别
|
23天前
|
搜索推荐 Java 数据库连接
Java|在 IDEA 里自动生成 MyBatis 模板代码
基于 MyBatis 开发的项目,新增数据库表以后,总是需要编写对应的 Entity、Mapper 和 Service 等等 Class 的代码,这些都是重复的工作,我们可以想一些办法来自动生成这些代码。
30 6
|
23天前
|
Java
通过Java代码解释成员变量(实例变量)和局部变量的区别
本文通过一个Java示例,详细解释了成员变量(实例变量)和局部变量的区别。成员变量属于类的一部分,每个对象有独立的副本;局部变量则在方法或代码块内部声明,作用范围仅限于此。示例代码展示了如何在类中声明和使用这两种变量。
|
23天前
|
设计模式 Java 程序员
[Java]23种设计模式
本文介绍了设计模式的概念及其七大原则,强调了设计模式在提高代码重用性、可读性、可扩展性和可靠性方面的作用。文章还简要概述了23种设计模式,并提供了进一步学习的资源链接。
36 0
[Java]23种设计模式
|
7天前
|
设计模式 JavaScript Java
Java设计模式:建造者模式详解
建造者模式是一种创建型设计模式,通过将复杂对象的构建过程与表示分离,使得相同的构建过程可以创建不同的表示。本文详细介绍了建造者模式的原理、背景、应用场景及实际Demo,帮助读者更好地理解和应用这一模式。