瑞_23种设计模式_解释器模式

发布于:2024-05-02 ⋅ 阅读:(30) ⋅ 点赞:(0)

🙊 前言:本文章为瑞_系列专栏之《23种设计模式》的解释器模式篇。本文中的部分图和概念等资料,来源于博主学习设计模式的相关网站《菜鸟教程 | 设计模式》《黑马程序员Java设计模式详解》,特此注明。本文中涉及到的软件设计模式的概念、背景、优点、分类、以及UML图的基本知识和设计模式的6大法则等知识,建议阅读 《瑞_23种设计模式_概述》

本系列 - 设计模式 - 链接:《瑞_23种设计模式_概述》

⬇️本系列 - 创建型模式 - 链接🔗

  单例模式:《瑞_23种设计模式_单例模式》
  工厂模式:《瑞_23种设计模式_工厂模式》
  原型模式:《瑞_23种设计模式_原型模式》
抽象工厂模式:《瑞_23种设计模式_抽象工厂模式》
 建造者模式:《瑞_23种设计模式_建造者模式》

⬇️本系列 - 结构型模式 - 链接🔗

  代理模式:《瑞_23种设计模式_代理模式》
 适配器模式:《瑞_23种设计模式_适配器模式》
 装饰者模式:《瑞_23种设计模式_装饰者模式》
  桥接模式:《瑞_23种设计模式_桥接模式》
  外观模式:《瑞_23种设计模式_外观模式》
  组合模式:《瑞_23种设计模式_组合模式》
  享元模式:《瑞_23种设计模式_享元模式》

⬇️本系列 - 行为型模式 - 链接🔗

模板方法模式:《瑞_23种设计模式_模板方法模式》
  策略模式:《瑞_23种设计模式_策略模式》
  命令模式:《瑞_23种设计模式_命令模式》
 职责链模式:《瑞_23种设计模式_职责链模式》
  状态模式:《瑞_23种设计模式_状态模式》
 观察者模式:《瑞_23种设计模式_观察者模式》
 中介者模式:《瑞_23种设计模式_中介者模式》
 迭代器模式:《瑞_23种设计模式_迭代器模式》
 访问者模式:《瑞_23种设计模式_访问者模式》
 备忘录模式:《瑞_23种设计模式_备忘录模式》
 解释器模式:《瑞_23种设计模式_解释器模式》

在这里插入图片描述

1 解释器模式(Interpreter Pattern)

  解释器模式(Interpreter Pattern)提供了评估语言的语法或表达式的方式,它属于行为型模式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等。

  瑞:行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
  瑞:行为型模式分为类行为模式对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性

解释器模式属于:类行为模式

1.1 介绍

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

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

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

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

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

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

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

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

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

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

1.2 概述

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

在这里插入图片描述

  如上图,设计一个软件用来进行加减计算。我们第一想法就是使用工具类,提供对应的加法和减法的工具方法。

// 用于两个整数相加
public static int add(int a,int b){
    return a + b;
}

// 用于两个整数相加
public static int add(int a,int b,int c){
    return a + b + c;
}

// 用于n个整数相加
public static int add(Integer ... arr) {
    int sum = 0;
    for (Integer i : arr) {
        sum += i;
    }
    return sum;
}

  上面的形式比较单一、有限,如果形式变化非常多,这就不符合要求,因为加法和减法运算,两个运算符与数值可以有无限种组合方式。比如 1+2+3+4+5、1+2+3-4等等。

  显然,现在需要一种翻译识别机器,能够解析由数字以及 + - 符号构成的合法的运算序列。如果把运算符和数字都看作节点的话,能够逐个节点的进行读取解析运算,这就是解释器模式的思维。

  在解释器模式中,我们需要将待解决的问题,提取出规则,抽象为一种“语言”。比如加减法运算,规则为:由数值和±符号组成的合法序列,“1+3-2” 就是这种语言的句子。

  解释器就是要解析出来语句的含义。描述规则主要有以下两种

1.2.1 文法(语法)规则

  文法是用于描述语言的语法结构的形式规则。

expression ::= value | plus | minus
plus ::= expression ‘+’ expression   
minus ::= expression ‘-’ expression  
value ::= integer

注意: 这里的符号::=表示“定义为”的意思,竖线 | 表示或,左右的其中一个,引号内为字符本身,引号外为语法。

  上面规则描述为 :

  表达式可以是一个值,也可以是 plus 或者 minus 运算,而 plus 和 minus 又是由表达式结合运算符构成,值的类型为整型数。

1.2.2 抽象语法树

  在计算机科学中,抽象语法树(AbstractSyntaxTree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。

  用树形来表示符合文法规则的句子。

在这里插入图片描述

1.3 解释器模式的结构

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

1.4 解释器模式的优缺点


优点

  • 易于改变和扩展文法。
    由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言。

  • 实现文法较为容易。
    在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂。

  • 增加新的解释表达式较为方便。
    如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式类,原有表达式类代码无须修改,符合 “开闭原则”。

缺点

  • 对于复杂文法难以维护。
    在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护。

  • 执行效率较低。
    由于在解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦。

1.5 解释器模式的使用场景

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



2 案例一

【案例】设计实现加减法的软件

2.1 需求

  设计一个软件用来进行加减法计算,类图如下:

在这里插入图片描述

2.2 代码实现

抽象表达式类(抽象类)
/**
 * 抽象表达式类
 *
 * @author LiaoYuXing-Ray
 **/
public abstract class AbstractExpression {

    public abstract int interpret(Context context);
}

加法表达式类(类)
/**
 * 加法表达式类 - 非终结符表达式角色
 *
 * @author LiaoYuXing-Ray
 **/
public class Plus extends AbstractExpression {

    // +号左边的表达式
    private final AbstractExpression left;
    // +号右边的表达式
    private final AbstractExpression right;

    public Plus(AbstractExpression left, AbstractExpression right) {
        this.left = left;
        this.right = right;
    }

    public int interpret(Context context) {
        // 将左边表达式的结果和右边表达式的结果进行相加
        return left.interpret(context) + right.interpret(context);
    }

    @Override
    public String toString() {
        return "(" + left.toString() + " + " + right.toString() + ")";
    }
}
减法表达式类(类)

/**
 * 减法表达式类 - 非终结符表达式角色
 *
 * @author LiaoYuXing-Ray
 **/
public class Minus extends AbstractExpression {

    // -号左边的表达式
    private final AbstractExpression left;
    // -号右边的表达式
    private final AbstractExpression right;

    public Minus(AbstractExpression left, AbstractExpression right) {
        this.left = left;
        this.right = right;
    }

    public int interpret(Context context) {
        // 将左边表达式的结果和右边表达式的结果进行相减
        return left.interpret(context) - right.interpret(context);
    }

    @Override
    public String toString() {
        return "(" + left.toString() + " - " + right.toString() + ")";
    }
}
变量表达式(类)
/**
 * 变量表达式 - 终结符表达式角色
 *
 * @author LiaoYuXing-Ray
 **/
public class Variable extends AbstractExpression {

    // 声明存储变量名的成员变量
    private final String name;

    public Variable(String name) {
        this.name = name;
    }

    public int interpret(Context context) {
        // 直接返回变量的值
        return context.getValue(this);
    }

    @Override
    public String toString() {
        return name;
    }
}
环境角色类(类)
import java.util.HashMap;
import java.util.Map;

/**
 * 环境角色类
 *
 * @author LiaoYuXing-Ray
 **/
public class Context {

    // 定义一个map集合,用来存储变量及对应的值
    private final Map<Variable, Integer> map = new HashMap<Variable, Integer>();

    // 添加变量的功能
    public void assign(Variable var, Integer value) {
        map.put(var, value);
    }

    // 根据变量获取对应的值
    public int getValue(Variable var) {
        return map.get(var);
    }
}

测试类
/**
 * 测试类
 *
 * @author LiaoYuXing-Ray
 **/
public class Client {
    public static void main(String[] args) {
        // 创建环境对象
        Context context = new Context();

        // 创建多个变量对象
        Variable a = new Variable("a");
        Variable b = new Variable("b");
        Variable c = new Variable("c");
        Variable d = new Variable("d");

        // 将变量存储到环境对象中
        context.assign(a, 1);
        context.assign(b, 2);
        context.assign(c, 3);
        context.assign(d, 4);

        // 获取抽象语法树    a + b - c + d
        AbstractExpression expression = new Minus(a, new Minus(new Minus(b, c), d));

        // 解释(计算)
        int result = expression.interpret(context);

        System.out.println(expression + " = " + result);
    }
}

  代码运行结果如下:

	(a - ((b - c) - d)) = 6

3 案例二

本案例为菜鸟教程中的案例

3.1 需求

  我们将创建一个接口 Expression 和实现了 Expression 接口的实体类。定义作为上下文中主要解释器的 TerminalExpression 类。其他的类 OrExpression、AndExpression 用于创建组合式表达式。

  InterpreterPatternDemo,我们的演示类使用 Expression 类创建规则和演示表达式的解析。

在这里插入图片描述

3.2 代码实现

步骤 1

  创建一个表达式接口。

Expression.java
public interface Expression {
   public boolean interpret(String context);
}

步骤 2

  创建实现了上述接口的实体类。

TerminalExpression.java
public class TerminalExpression implements Expression {
   
   private String data;
 
   public TerminalExpression(String data){
      this.data = data; 
   }
 
   @Override
   public boolean interpret(String context) {
      if(context.contains(data)){
         return true;
      }
      return false;
   }
}
OrExpression.java
public class OrExpression implements Expression {
    
   private Expression expr1 = null;
   private Expression expr2 = null;
 
   public OrExpression(Expression expr1, Expression expr2) { 
      this.expr1 = expr1;
      this.expr2 = expr2;
   }
 
   @Override
   public boolean interpret(String context) {      
      return expr1.interpret(context) || expr2.interpret(context);
   }
}
AndExpression.java
public class AndExpression implements Expression {
    
   private Expression expr1 = null;
   private Expression expr2 = null;
 
   public AndExpression(Expression expr1, Expression expr2) { 
      this.expr1 = expr1;
      this.expr2 = expr2;
   }
 
   @Override
   public boolean interpret(String context) {      
      return expr1.interpret(context) && expr2.interpret(context);
   }
}

步骤 3

  InterpreterPatternDemo 使用 Expression 类来创建规则,并解析它们。

InterpreterPatternDemo.java
public class InterpreterPatternDemo {
 
   //规则:Robert 和 John 是男性
   public static Expression getMaleExpression(){
      Expression robert = new TerminalExpression("Robert");
      Expression john = new TerminalExpression("John");
      return new OrExpression(robert, john);    
   }
 
   //规则:Julie 是一个已婚的女性
   public static Expression getMarriedWomanExpression(){
      Expression julie = new TerminalExpression("Julie");
      Expression married = new TerminalExpression("Married");
      return new AndExpression(julie, married);    
   }
 
   public static void main(String[] args) {
      Expression isMale = getMaleExpression();
      Expression isMarriedWoman = getMarriedWomanExpression();
 
      System.out.println("John is male? " + isMale.interpret("John"));
      System.out.println("Julie is a married women? " 
      + isMarriedWoman.interpret("Married Julie"));
   }
}

步骤 4

  执行程序,输出结果:

	John is male? true
	Julie is a married women? true



本文是博主的粗浅理解,可能存在一些错误或不完善之处,如有遗漏或错误欢迎各位补充,谢谢

  如果觉得这篇文章对您有所帮助的话,请动动小手点波关注💗,你的点赞👍收藏⭐️转发🔗评论📝都是对博主最好的支持~