Java设计模式之行为型-解释器模式(UML类图+案例分析)

目录

一、基础概念

二、UML类图

三、角色设计

四、案例分析

五、总结


一、基础概念

解释器模式是指给定一个语言(表达式),来表示它的文法,并定义一个解释器,使用该解释器来解释语言中的句子(表达式),并得到结果。简单来说,解释器模式把要处理的问题条条框框地定义出来,然后挨个细致解释,适合一些重复性的问题求解。

所谓终结符,是指表达式中的最小单位,它不再包含其他更小的表达式,可以理解为表达式的"叶子"节点,例如在一个简单的表达式计算器中:

1、数字值(如123)就是一个终结符,数字解释器实现了对数字的解释操作。

2、变量名(如a)也可以看作是一个终结符,变量解释器实现获取变量的值。

非终结符需要通过组合包含的子表达式的结果进行解释,例如:

1、加法表达式包含左边和右边两个子表达式,通过组合两个子表达式的计算结果实现加法。

2、函数调用表达式包含函数名和参数表达式,需要调用函数并传入参数计算结果。

举个例子:"5 + 6",然后解析这个表达式,计算出结果11。

在这个例子中:

1、数字5和6是终结符,它们是表达式的基本元素,不再包含其他表达式。

2、加号是非终结符,它表示一个表达式,需要通过计算5和6才能得出值。

所以简单来说:

1、终结符是表达式的基本元素,不再含其他表达式。

2、非终结符表示一个表达式,包含其他表达式。

二、UML类图

三、角色设计

角色 描述
抽象表达式角色 定义解释器的接口,声明一个解释操作interpret()
终结符表达式角色 实现抽象表达式接口,表示语法规则中的终结符
非终结符表达式角色 实现抽象表达式接口,表示语法规则中的非终结符,可以包含终结符和非终结符
客户端角色 建立解释器对象,并调用interpret()方法进行解释操作

四、案例分析

下面我们通过模拟一个算术表达式的运算过程来实现解释器模式。

大体的工作流程如下:

1、构建表达式语法树。

2、用递归解释器对语法树进行解释执行。

3、终结符表达式直接返回值,非终结符表达式通过组合运算达到计算功能。

4、客户端构建语法树并调用解释器执行,即完成了对表达式的解析和求值。

代码设计如下:

定义一个抽象表达式角色,并定义了解释操作接口interpret():

java 复制代码
public interface Expression {

    int interpret();

}

定义AddExpression,SubtractExpression等,非终结符表达式角色,它们实现了对复合表达式的解释,通过组合包含的子表达式的解释结果进行解释:

java 复制代码
public class AddExpression implements Expression {
    private Expression left;
    private Expression right;

    public AddExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpret() {
        return left.interpret() + right.interpret();
    }
}

public class SubtractExpression implements Expression {
    private Expression left;
    private Expression right;

    public SubtractExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpret() {
        return left.interpret() - right.interpret();
    }
}

public class MultiplyExpression implements Expression {
    private Expression left;
    private Expression right;

    public MultiplyExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpret() {
        return left.interpret() * right.interpret();
    }
}

public class DivideExpression implements Expression {
    private Expression left;
    private Expression right;

    public DivideExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpret() {
        try {
            return left.interpret() / right.interpret();
        } catch (ArithmeticException e) {
            System.out.println("除数不能为0");
            return -1;
        }
    }
}

定义NumberExpression,终结符表达式角色,它实现了对终结符的解释,这里的终结符是数字,直接返回数字的值:

java 复制代码
public class NumberExpression implements Expression {

    private int number;

    public NumberExpression(int number) {
        this.number = number;
    }

    @Override
    public int interpret() {
        return number;
    }
    
}

客户端角色,添加一个Interpreter类,来解析表达式字符串,构建抽象语法树,并调用interpret()方法:

java 复制代码
public class Client {

    public static void main(String[] args) {
        Expression expression = new SubtractExpression(
                new AddExpression(new NumberExpression(5),
                        new MultiplyExpression(new NumberExpression(10), new NumberExpression(15))),
                new DivideExpression(new NumberExpression(5), new NumberExpression(5))
        );
        int result = expression.interpret();
        System.out.println("计算结果为:" + result);

    }
}

运行结果如下:

执行流程如下:

1、在客户端中,首先构建一个减法表达式SubtractExpression,它的左表达式是加法表达式AddExpression,右表达式是除法表达式DivideExpression。

2、加法表达式AddExpression的左表达式是数字表达式NumberExpression(5),右表达式是乘法表达式MultiplyExpression。

3、乘法表达式MultiplyExpression的左表达式是数字表达式NumberExpression(10),右表达式是数字表达式NumberExpression(15)。

4、除法表达式DivideExpression的左表达式是数字表达式NumberExpression(5),右表达式是数字表达式NumberExpression(5)。

5、这样就构建出了一个完整的表达式树。

6、然后调用这个减法表达式的interpret()方法进行解释执行。

7、interpret()方法会递归调用子表达式的interpret()方法,终结于数字表达式的interpret()只返回数字的值。

8、这样整个表达式树就得到了解释执行,得到最终结果。

9、这里的执行顺序是:数字表达式->乘法表达式->加法表达式->除法表达式->减法表达式,逐步对表达式树进行解释,得到最终的值。

所以这个案例利用了解释器模式,定义了表达式接口,并为不同的表达式实现了interpret()方法,通过组合模式构建表达式树,然后递归解释执行以求值。

五、总结

优点:

1、可扩展性好。新增解释表达式较为方便,符合开闭原则。

2、易于改变和扩展语法。比如增加新的运算符等。

3、将语法解释与执行分离,允许程序动态地定制,每个节点只需要处理自身逻辑。

4、各部分功能模块较为独立,有利于实现和维护。

5、使用递归解释方式简化了语法树的evaluate逻辑。

缺点:

1、解释过程效率较低,递归调用开销较大。

2、类数量多,之间依赖关系复杂,导致系统较难理解。

3、语法树构建较为麻烦,需要客户端理解语法结构。

4、语法树的解析需要定义大量的类,不易管理。

5、语言扩展较难,需要更改多个表达式类。

6、错误处理不容易,需要增加额外处理。

7、调试过程较复杂。

应用场景:

1、语言解释器:用于解析和解释语言中的表达式,如编译器、解释器等。这个代码案例就展示了如何通过解释器模式解析算术表达式。

2、表达式求值:用于进行表达式求值,如正则表达式、数学表达式、逻辑表达式等的求值运算。

3、符号处理引擎:可用来进行数学符号、表达式的解析。

4、语法分析:可构建语法树,用来分析句子或表达式所遵循的语法规则。

5、业务规则解析:可定义业务规则语法,并进行解释执行,用来实现规则引擎。

6、格式转换器:可将一种格式转换为另一种格式。

符合的设计原则:

1、开闭原则(Open Closed Principle)

解释器模式中的解释器语法可以通过继承扩展,而无需修改原有解释器代码,符合开闭原则。

2、单一职责原则(Single Responsibility Principle)

解释器模式把语法解析的任务分发给不同的解释器类,每一个解释器只负责一小部分语法规则,符合单一职责原则。

3、组合复用原则(Composite Reuse Principle)

解释器模式中的解释器可以通过组合方式重用,符合组合复用原则。

4、接口隔离原则(Interface Segregation Principle)

解释器模式通过定义解释器接口将解析语法和解释执行分离,符合接口隔离原则。

5、依赖倒转原则(Dependence Inversion Principle)

解释器类都依赖一个抽象语法树而不是具体语法类,符合依赖倒转原则。

相关推荐
南星沐1 小时前
Spring Boot 常用依赖介绍
java·前端·spring boot
代码不停2 小时前
Java中的异常
java·开发语言
何似在人间5752 小时前
多级缓存模型设计
java·jvm·redis·缓存
未定义.2212 小时前
UML-银行取款序列图
设计模式·流程图·软件工程·需求分析·uml
多云的夏天2 小时前
ubuntu24.04-MyEclipse的项目导入到 IDEA中
java·intellij-idea·myeclipse
Fanxt_Ja2 小时前
【数据结构】红黑树超详解 ---一篇通关红黑树原理(含源码解析+动态构建红黑树)
java·数据结构·算法·红黑树
Aphelios3803 小时前
TaskFlow开发日记 #1 - 原生JS实现智能Todo组件
java·开发语言·前端·javascript·ecmascript·todo
weixin_448771723 小时前
使用xml模板导出excel
xml·java·excel
烁3474 小时前
每日一题(小白)模拟娱乐篇27
java·数据结构·算法·娱乐
魔道不误砍柴功4 小时前
2025年Java无服务器架构实战:AWS Lambda与Spring Cloud Function深度整合
java·架构·serverless