【设计模式——学习笔记】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】

java 复制代码
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);
}

【变量解析器】

java 复制代码
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);
   }
}

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

java 复制代码
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;
   }
}

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

java 复制代码
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);
   }
}

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

java 复制代码
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);
    }
}

【计算器】

java 复制代码
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);
   }
}

【客户端】

java 复制代码
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;
    }
}

【运行】

java 复制代码
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放入到栈中
  • 第二次循环: 从stack取出左表达式"a",接着中数组中获取并生成新的表达式"b",最后构建加法表达式"a+b"存储到栈中

案例二

说明

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

类图

实现

【抽象表达式:Node】

java 复制代码
package com.atguigu.interpreter.Sample;

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

【自定义的 解析异常】

java 复制代码
package com.atguigu.interpreter.Sample;

public class ParseException extends Exception {
    public ParseException(String msg) {
        super(msg);
    }
}

【终结符表达式:PrimitiveCommandNode】

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

java 复制代码
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】

java 复制代码
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】

java 复制代码
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】

java 复制代码
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】

java 复制代码
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】

java 复制代码
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】

java 复制代码
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】

SQL 复制代码
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

【运行】

java 复制代码
迷你程序 = "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】

java 复制代码
package com.atguigu.interpreter.A1.language;

public class ParseException extends Exception {
    public ParseException(String msg) {
        super(msg);
    }
}

【ExecuteException】

java 复制代码
package com.atguigu.interpreter.A1.language;

public class ExecuteException extends Exception {
    public ExecuteException(String msg) {
        super(msg);
    }
}

【Node】

java 复制代码
package com.atguigu.interpreter.A1.language;

/**
 * 实现Executor执行器接口
 */
public abstract class Node implements Executor {
    public abstract void parse(Context context) throws ParseException;
}

【ProgramNode】

java 复制代码
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】

java 复制代码
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】

java 复制代码
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】

java 复制代码
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】

java 复制代码
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】

java 复制代码
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】

java 复制代码
package com.atguigu.interpreter.A1.language;

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

【InterpreterFacade】

java 复制代码
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】

java 复制代码
package com.atguigu.interpreter.A1.language;

public interface ExecutorFactory {
    /**
     * 创建一个执行器
     * @param name
     * @return
     */
    public abstract Executor createExecutor(String name);
}

【TurtleCanvas】

java 复制代码
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】

java 复制代码
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框架中的应用

java 复制代码
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),进而综合两者的内容,让知识点更加全面
相关推荐
ke_wu4 小时前
结构型设计模式
开发语言·设计模式·组合模式·简单工厂模式·工厂方法模式·抽象工厂模式·装饰器模式
小马爱打代码4 小时前
设计模式详解(建造者模式)
java·设计模式·建造者模式
小王爱吃月亮糖4 小时前
C++的23种设计模式
开发语言·c++·qt·算法·设计模式·ecmascript
B1nna4 小时前
Redis学习(三)缓存
redis·学习·缓存
_im.m.z4 小时前
【设计模式学习笔记】1. 设计模式概述
笔记·学习·设计模式
胡西风_foxww6 小时前
【ES6复习笔记】迭代器(10)
前端·笔记·迭代器·es6·iterator
左漫在成长7 小时前
王佩丰24节Excel学习笔记——第十九讲:Indirect函数
笔记·学习·excel
纪伊路上盛名在7 小时前
Max AI prompt1
笔记·学习·学习方法
Suwg2098 小时前
【MySQL】踩坑笔记——保存带有换行符等特殊字符的数据,需要进行转义保存
数据库·笔记·mysql
2401_857610038 小时前
中文学习系统:成本效益分析与系统优化
java·数据库·学习·架构