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)

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

相关推荐
战神刘玉栋9 分钟前
《程序猿之设计模式实战 · 观察者模式》
python·观察者模式·设计模式
陈大爷(有低保)1 小时前
UDP Socket聊天室(Java)
java·网络协议·udp
nakyoooooo1 小时前
【设计模式】工厂模式、单例模式、观察者模式、发布订阅模式
观察者模式·单例模式·设计模式
kinlon.liu1 小时前
零信任安全架构--持续验证
java·安全·安全架构·mfa·持续验证
王哲晓1 小时前
Linux通过yum安装Docker
java·linux·docker
java6666688882 小时前
如何在Java中实现高效的对象映射:Dozer与MapStruct的比较与优化
java·开发语言
Violet永存2 小时前
源码分析:LinkedList
java·开发语言
执键行天涯2 小时前
【经验帖】JAVA中同方法,两次调用Mybatis,一次更新,一次查询,同一事务,第一次修改对第二次的可见性如何
java·数据库·mybatis
Jarlen2 小时前
将本地离线Jar包上传到Maven远程私库上,供项目编译使用
java·maven·jar
蓑 羽2 小时前
力扣438 找到字符串中所有字母异位词 Java版本
java·算法·leetcode