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

news2024/11/22 8:45:19

案例引入

通过解释器模式来实现四则运算,如计算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放入到栈中
  • 第二次循环: 从stack取出左表达式"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 + "]";
    }
}

【非终结符表达式: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),进而综合两者的内容,让知识点更加全面

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/871770.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue中有趣的几个功能

vue中有趣的几个功能 老实说&#xff0c;我们大多数人都不太喜欢阅读文档&#xff0c;但是当使用像 Vue 这样不断发展的现代前端框架时&#xff0c;每个新版本都会发生很多变化&#xff0c;我们可能会错过一些后来推出的新的、闪亮的功能。让我们来看看那些有趣但不那么受欢迎…

【Windows 常用工具系列 5 -- Selenium IDE的使用方法 】

文章目录 Selenium 介绍Selenium IDE 介绍 Selenium IDE安装Chrome 浏览器安装Selenium IDE使用 Selenium 介绍 Selenium是一个用于Web应用程序测试的工具。Selenium测试直接运行在浏览器中&#xff0c;就像真正的用户在操作一样。 Selenium家庭成员有三个&#xff0c;分别是S…

并发冲突导致流量放大的线上问题解决

事故现象 生产环境&#xff0c;转账相关请求失败量暴增。 直接原因 现网多个重试请求同时到达 svr&#xff0c;导致内存数据库大量返回时间戳冲突。业务方收到时间戳冲突&#xff0c;自动进行业务重试&#xff0c;服务内部也存在重试&#xff0c;导致流量放大。 转账 首先…

【CI/CD】基于 Jenkins+Docker+Git 的简单 CI 流程实践(上)

基于 JenkinsDockerGit 的简单 CI 流程实践&#xff08;上&#xff09; 在如今的互联网时代&#xff0c;随着软件开发复杂度的不断提高&#xff0c;软件开发和发布管理也越来越重要。目前已经形成一套标准的流程&#xff0c;最重要的组成部分就是 持续集成 及 持续交付、部署。…

用对角线去遍历矩阵

原题链接 用对角线遍历矩阵https://leetcode.cn/leetbook/read/array-and-string/cuxq3/ 算法分析 图一 图二 图三 图四 由上述四个图可以总结得出以下八个结论&#xff1a; 结论1&#xff1a;k属于[0,a(max)b(max)]。 结论2&#xff1a;每一层遍历行最多存在min(m,n)个矩…

深度学习实战基础案例——卷积神经网络(CNN)基于SqueezeNet的眼疾识别|第1例

文章目录 前言一、数据准备1.1 数据集介绍1.2 数据集文件结构 二、项目实战2.1 数据标签划分2.2 数据预处理2.3 构建模型2.4 开始训练2.5 结果可视化 三、数据集个体预测 前言 SqueezeNet是一种轻量且高效的CNN模型&#xff0c;它参数比AlexNet少50倍&#xff0c;但模型性能&a…

4.深入对象

4.1创建对象三种方式 1.利用对象字面量创建对象 const obj{ name : 佩奇 }2.利用new 0bject创建对象 const obj new Object({ name: 佩奇 }) console.log(obj) // {name: 佩奇}3.利用构造函数创建对象 4.2构造函数 构造函数&#xff1a;是一种特殊的函数,主要用来初始化…

hive 中最常用日期处理函数

hive 常用日期处理函数 在工作中&#xff0c;日期函数是提取数据计算数据必须要用到的环节。哪怕是提取某个时间段下的明细数据也得用到日期函数。今天和大家分享一下常用的日期函数。为什么说常用呢&#xff1f;其实这些函数在数据运营同学手上是几乎每天都在使用的。 技术交…

Metasploit教程 - 基本命令

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、metasploit版本二、命令1.help2.msfupdate 命令3.search命令 三.Armitage GUI四&#xff0c;扫描总结 我想我得对我的博客做一些美化&#xff0c;而不是只有…

【Power BI】使用 Power BI 处理结构化复杂表单数据 | 文末送书

文章目录 前言使用 Power BI 处理结构化复杂表单数据案例一、处理标题与内容同行的数据表案例二、处理标题与内容同单元格的数据表 文末总结Power BI 新书推荐 前言 数据处理是数据分析的奠基石&#xff0c;只有使用处理干净的数据&#xff0c;分析才会产生价值。简单而言&…

Nacos AP架构集群搭建(Windows)

手写SpringCloud项目地址&#xff0c;求个star github:https://github.com/huangjianguo2000/spring-cloud-lightweight gitee:https://gitee.com/huangjianguo2000/spring-cloud-lightweigh 目录&#xff1a; 一&#xff1a;初始化MySQL 二&#xff1a;复制粘贴三份Nacos文…

【设计模式】拦截过滤器模式

拦截过滤器模式&#xff08;Intercepting Filter Pattern&#xff09;用于对应用程序的请求或响应做一些预处理/后处理。定义过滤器&#xff0c;并在把请求传给实际目标应用程序之前应用在请求上。过滤器可以做认证/授权/记录日志&#xff0c;或者跟踪请求&#xff0c;然后把请…

Docker启动相关的命令

1.Docker服务相关命令 操作daemon CentOS7版本启动docker systemctl start docker systemctl status docker停掉docker服务&#xff1a; systemctl stop docker重启命令 systemctl restart docker开机自启docker systemctl enable docker2.小结

8.13黄金是否进入下行通道?下周开盘如何布局

近期有哪些消息面影响黄金走势&#xff1f;黄金多空该如何研判&#xff1f; ​黄金消息面解析&#xff1a;周五(8月11日)现货黄金小幅收低&#xff0c;受累于美元走强和美国国债收益率上升&#xff0c;本周录得6月底以来最差单周表现。投资者在评估最新一批通胀报告和消费者信…

欧拉公式之证明

首先&#xff0c;我们考虑复数函数的泰勒级数展开式。对于任意一个复数函数f(z)&#xff0c;我们可以将其在za处进行泰勒级数展开&#xff1a; f(z) f(a) f(a)(z-a) f(a)(z-a)^2/2! f(a)(z-a)^3/3! ... 其中f(a)表示f(z)在za处的导数&#xff0c;f(a)表示f(z)在…

Docker查看、创建、进入容器相关的命令

1.查看、创建、进入容器的指令 用-it指令创建出来的容器&#xff0c;创建完成之后会立马进入容器。退出之后立马关闭容器。 docker run -it --namec1 centos:7 /bin/bash退出容器&#xff1a; exit查看现在正在运行的容器命令&#xff1a; docker ps查看历史容器&#xff0…

【前端】求职必备知识点4-CSS:flex、隐藏元素(7种方法)、单位

文章目录 flex隐藏元素&#xff08;7种方法&#xff09;不占位置占位置 单位思维导图 flex 【前端】CSS3弹性布局&#xff08;flex&#xff09;、媒体查询实现响应式布局和自适应布局_css媒体查询 自适应_karshey的博客-CSDN博客 flex缩写&#xff1a; flex-grow 和 flex-shr…

聚类与回归

聚类 聚类属于非监督式学习&#xff08;无监督学习&#xff09;&#xff0c;往往不知道因变量。 通过观察学习&#xff0c;将数据分割成多个簇。 回归 回归属于监督式学习&#xff08;有监督学习&#xff09;&#xff0c;知道因变量。 通过有标签样本的学习分类器 聚类和…

vs使用def导出文件简介

vs使用def导出文件简介 1.首先需要创建一个dll项目&#xff0c;否则没地方配置使用def文件的指向 2.定义一系列函数并创建一个def文件 3.配置使用def文件的指向编译即可 配置到导出格式时候可以通过NONANE选项使到处函数的符号名字为空&#xff0c;X为导出的序号&#xff0c…