【愚公系列】2023年10月 二十三种设计模式(十五)-解释器模式(Interpreter Pattern)
🏆 作者简介,愚公搬代码
🏆《头衔》:华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,阿里云专家博主,腾讯云优秀博主,掘金优秀博主,51CTO博客专家等。
🏆《近期荣誉》:2022年CSDN博客之星TOP2,2022年华为云十佳博主等。
🏆《博客内容》:.NET、Java、Python、Go、Node、前端、IOS、Android、鸿蒙、Linux、物联网、网络安全、大数据、人工智能、U3D游戏、小程序等相关领域知识。
🏆🎉欢迎 👍点赞✍评论⭐收藏
🚀前言
设计模式(Design Pattern)是软件开发领域的宝贵经验,是多人反复借鉴和广泛应用的代码设计指导。它们是一系列经过分类和归纳的代码组织方法,旨在实现可重用性、可维护性和可理解性。使用设计模式,我们能够编写高质量的代码,使其更易于他人理解,并提供了代码可靠性的保证。
毫无疑问,设计模式对个人、团队和整个系统都带来了显著的益处。它们将代码开发提升到工程化水平,为软件工程构建提供了坚实的基础,就如同大厦的一块块精巧的砖石一样。在项目中明智地应用设计模式可以完美地解决各种复杂问题。每种设计模式都有相应的原理和最佳实践,它们描述了我们日常开发中不断遇到的问题,以及这些问题的核心解决方法。正是因为这种实用性和通用性,设计模式才能在软件开发中广泛地得以应用。设计模式是构建稳健、可扩展和可维护软件的关键工具,为开发者们提供了解决问题的智慧和指导。
🚀一、解释器模式(Interpreter Pattern)
解释器模式属于行为型设计模式,它用于定义一种语言的文法表示,并提供了一种解释器来解释这种语言中的句子。
解释器模式的核心思想是将语言的文法规则表示成对象,并且提供了一种解释器来解释这些对象。这样,我们可以通过构建不同的文法对象和组合它们来表示复杂的语法结构,并且可以用解释器来执行这些语法结构。
主要要点:
文法表示:首先,我们需要定义特定语言的文法表示,通常将文法规则抽象成对象。每个文法规则对应一个具体的类或接口。
解释器接口:定义一个解释器接口,该接口通常包含解释方法,用于解释文法对象。这个接口可以有多个实现,每个实现对应一种语法规则。
上下文:解释器模式需要一个上下文对象,它包含了解释器需要的信息和状态。解释器可以访问和修改上下文来执行解释操作。
构建表达式:通过构建不同的文法对象,并将它们组合成一个表达式树来表示一个语法句子。表达式树的根节点是整个语法句子的解释器。
解释操作:通过调用解释器的解释方法来执行语法句子的解释操作。解释器递归地遍历表达式树,执行具体的解释逻辑。
应用领域:解释器模式广泛应用于需要解析和执行特定语言或规则的领域,例如SQL解析、数学表达式求值、正则表达式匹配等。
解释器模式提供了一种灵活的方式来处理语法解释和执行,使得我们可以轻松地扩展和修改支持的语法规则。它在那些需要构建自定义语言或处理复杂语法的应用中非常有价值。
🚀二、使用步骤
🔎1.角色
🦋1.1 抽象表达式(Expression)
在解释器模式(Interpreter Pattern)中,抽象表达式(Expression)是一个关键概念,它代表了语言中的文法规则的抽象表示,具有以下作用和特点:
文法规则的抽象:抽象表达式是对语言中的文法规则的抽象表示。每个具体的文法规则都会对应一个具体的抽象表达式类。
解释方法:抽象表达式通常包含一个解释方法(interpret),这个方法用于解释具体的文法规则。解释方法的实现是根据文法规则的具体要求来完成的。
构建表达式树:在解释器模式中,可以通过构建一个表达式树(Expression Tree)来表示一个语法句子。表达式树的节点是抽象表达式对象,叶子节点通常是终结符表达式(Terminal Expression),而非叶子节点是非终结符表达式(Non-terminal Expression)。
递归解释:解释器模式通常使用递归的方式来解释表达式树。从根节点开始,递归地调用子节点的解释方法,直到叶子节点完成解释。
支持多种文法规则:通过定义不同的具体抽象表达式类,可以支持多种不同的文法规则。每个具体抽象表达式类负责解释特定的文法规则。
扩展性:解释器模式具有良好的扩展性。如果需要添加新的文法规则,只需创建新的具体抽象表达式类,并实现相应的解释逻辑,而不需要修改现有的解释器代码。
解耦语法解释和执行:解释器模式将语法解释和执行分离开来,使得我们可以独立地处理语法规则的解释逻辑,从而提高了代码的可维护性和可扩展性。
抽象表达式在解释器模式中充当了核心角色,它们代表了语言中的文法规则,并提供了解释这些规则的方法。通过组合和递归使用抽象表达式,可以构建出复杂的表达式树,从而实现对语法句子的解释和执行。这使得解释器模式在处理自定义语言、规则引擎和各种领域的语法解析中非常有用。
🦋1.2 终结符表达式(Terminal Expression)
在解释器模式(Interpreter Pattern)中,终结符表达式(Terminal Expression)是一种抽象表达式(Expression)的特殊类型,具有以下概念和作用:
终结符表达式的概念:
- 终结符表达式表示语言中的最基本的文法规则或语法单元,它不再包含其他子表达式。终结符通常对应语言中的终结符号,例如变量、常量、关键字、符号等。
解释方法:
- 终结符表达式实现了抽象表达式中的解释方法(interpret)。这个方法用于实际执行文法规则的解释操作。对于终结符表达式来说,解释方法通常返回终结符的值或执行特定操作。
作为表达式树的叶子节点:
- 在构建表达式树(Expression Tree)时,终结符表达式通常充当叶子节点。这是因为终结符表达式不再包含其他子表达式,它们代表语法树中的最底层的语法单元。
不再展开:
- 与非终结符表达式(Non-terminal Expression)不同,终结符表达式不再展开或递归调用其他表达式。它们的解释方法直接返回终结符的值或执行特定的操作。
支持语法解释:
- 终结符表达式的作用是支持语法解释的最终步骤。当解释器模式递归地解释表达式树时,最终会到达终结符表达式,这里执行实际的语法解释操作。
举例:
- 在一个简单的算术表达式解释器中,终结符表达式可以代表数字或变量。例如,当解释表达式 “2 + x” 时,“2” 和 “x” 可以分别作为终结符表达式,分别返回数字2和变量x的值。
终结符表达式在解释器模式中代表了语法规则中的最基本的、不可再分的语法单元,它们的主要作用是执行最终的语法解释操作,通常作为表达式树的叶子节点。这有助于将复杂的语法结构分解为更简单的组成部分,以实现对语法句子的完整解释。
🦋1.3 非终结符表达式(Nonterminal Expression)
在解释器模式(Interpreter Pattern)中,非终结符表达式(Nonterminal Expression)是一种抽象表达式(Expression)的特殊类型,具有以下概念和作用:
非终结符表达式的概念:
- 非终结符表达式表示语言中的复合文法规则或语法结构,它可以包含其他子表达式,包括终结符表达式和其他非终结符表达式。非终结符通常对应语言中的复杂语法规则,如句子、表达式、语句等。
解释方法:
- 非终结符表达式实现了抽象表达式中的解释方法(interpret)。这个方法用于解释复杂的语法结构,通常包括对子表达式的递归调用,以完成对整个语法结构的解释。
作为表达式树的中间节点:
- 在构建表达式树(Expression Tree)时,非终结符表达式充当中间节点。它们的子表达式可以是终结符表达式或其他非终结符表达式,形成了树状的结构,用于表示复杂的语法规则。
递归解释:
- 非终结符表达式通常需要递归地调用其子表达式的解释方法,以完成对整个语法结构的解释。这种递归结构使得解释器模式能够处理复杂嵌套的语法规则。
支持语法解释:
- 非终结符表达式的主要作用是支持语法解释的复杂部分。当解释器模式递归地解释表达式树时,非终结符表达式负责将各个子表达式组合成整体,以实现对复杂语法结构的解释。
举例:
- 在一个简单的条件语句解释器中,条件语句的非终结符表达式可以包含子表达式,如条件表达式和语句块。这些子表达式可以是终结符表达式(如变量、运算符等)或其他非终结符表达式(如逻辑表达式、赋值语句等)。
非终结符表达式在解释器模式中代表了语言中的复杂语法结构,它们的主要作用是组合和解释子表达式,以实现对整个语法结构的解释。非终结符表达式通常包含递归调用,以处理嵌套的语法规则,使得解释器模式能够处理复杂的语法解释任务。
🦋1.4 环境(Context)
在解释器模式(Interpreter Pattern)中,环境(Context)是一个重要的概念,它具有以下概念和作用:
环境的概念:
- 环境是解释器模式中的一个承载对象,它包含了解释器所需的信息,用于解释和评估表达式。环境对象通常包括了变量的值、解释器的配置信息和其他上下文相关的数据。
存储变量值:
- 一个主要作用是存储和管理变量的值。当解释器解释包含变量的表达式时,需要在环境中查找变量的值,并将其传递给解释器进行计算。
提供接口给解释器:
- 环境提供了接口供解释器访问变量值和其他上下文信息。解释器通过环境对象获取所需的数据,以便执行解释操作。
修改变量值:
- 环境允许在运行时修改变量的值。这对于支持动态变量赋值和更新非常有用。解释器可以通过环境对象来修改变量的值。
隔离解释器和上下文:
- 环境的存在使解释器和上下文相互隔离。解释器不需要直接访问外部变量或上下文,而是通过环境提供的接口来获取所需的数据,从而实现了解释器和上下文的解耦。
支持多次解释:
- 环境允许多次使用相同的解释器来解释不同的表达式,因为环境中的数据可以在每次解释中不同。
全局上下文管理:
- 在某些情况下,环境可以用于管理全局上下文信息,例如应用程序的配置参数、全局变量等。
环境在解释器模式中充当了存储和提供解释器所需数据的角色,它用于隔离解释器和上下文,同时支持多次解释不同的表达式。通过使用环境,可以使解释器模式更加灵活和可扩展,同时保持解释器的独立性,使其能够适应不同的应用场景。
🔎2.示例
命名空间InterpreterPattern中包含IWord抽象表达式接口,4个终结符表达式和1个非终结符表达式,Instruction类代表1条完整的指令,Semicolon类分隔左右两边的指令,Interpreter类充当环境类以构建表达式树并调用抽象表达式接口的解释方法Interpret。本案例尝试通过控制一次飞机的起飞至降落的过程来讲述解释器模式的使用方法。以下是我们要解释的指令:
340.00 10.00 taxing 1.00;
27.00 120.00 takeoff 1.00;
90.00 350.00 fly 30.00;
180.00 400.00 cruise 230.00;
50.00 320.00 fly 20.00;
320.00 110.00 landing 3.00;
120.00 10.00 taxing 3.00;
以上是我们要解释的所有7条指令,所有指令在同一行上,分号后是没有换行的,因为文章排版需要加了换行。以第1行为例解释每个参数的含义。340.00代表航向,10.00代表空速,taxing代表飞机的运动类型,1.00代表航程。
public interface IWord {
string Interpret();
}
定义抽象表达式接口IWord,包含一个Interpret方法。
public sealed class Course : IWord {
private double _course = 0;
public Course(double course) {
this._course = course;
}
public string Interpret() {
return $"heading:{_course}°,";
}
}
航向解释类Course,终结符表达式。
public sealed class Speed : IWord {
private double _speed = 0;
public Speed(double speed) {
this._speed = speed;
}
public string Interpret() {
return "speed:" + _speed.ToString() + "kn,";
}
}
空速解释类Speed,终结符表达式。
public sealed class Movement : IWord {
private string _movement = String.Empty;
public Movement(string movement) {
this._movement = movement;
}
private Dictionary<string, string> _movements = new Dictionary<string, string> {
{"taxing","taxing on the runway"},
{"takeoff","take off from the runway"},
{"fly","flying in the sky"},
{"cruise","navigate a cruise"},
{"landing","landing on the runway"},
};
public string Interpret() {
return "movement:" + _movements[_movement] + ",";
}
}
运动解释类Movement,终结符表达式。
public sealed class Voyage : IWord {
private double _voyage = 0;
public Voyage(double voyage) {
this._voyage = voyage;
}
public string Interpret() {
return "voyage:" + _voyage.ToString() + "km.";
}
}
航程解释类Voyage,终结符表达式。
public sealed class Semicolon : IWord {
private IWord _left = null;
private IWord _right = null;
public Semicolon(IWord left, IWord right) {
this._left = left;
this._right = right;
}
public string Interpret() {
return _left.Interpret() + Environment.NewLine + _right.Interpret();
}
}
分号解释类Semicolon,非终结符表达式。
public sealed class Instruction : IWord {
private IWord _course = null;
private IWord _speed = null;
private IWord _movement = null;
private IWord _voyage = null;
public Instruction(IWord course, IWord speed, IWord movement, IWord voyage) {
this._course = course;
this._speed = speed;
this._movement = movement;
this._voyage = voyage;
}
public string Interpret() {
return _course.Interpret() +
_speed.Interpret() +
_movement.Interpret() +
_voyage.Interpret();
}
}
由非终结符表达式分隔开的所有终结符表达式构成一条完整的指令Instruction类,这个类包含一个解释方法Interpret。
public class Interpreter {
private IWord _word = null;
private Instruction _instruction = null;
public string Interpret(string instruction) {
string[] instrucs = instruction.Split(';');
foreach(var word in instrucs) {
if(word.Trim() == "") break;
string[] words = word.Split(' ');
_instruction = new Instruction(new Course(double.Parse(words[0])),
new Speed(double.Parse(words[1])),
new Movement(words[2]),
new Voyage(double.Parse(words[3])));
if(_word == null) {
_word = _instruction;
} else {
_word = new Semicolon(_word, _instruction);
}
}
return _word.Interpret();
}
}
解释类Interpreter,充当环境类,此类最终构建一个表达式树并完成所有指令的解释动作。
public class Program {
private static Interpreter _interpreter = new Interpreter();
public static void Main(string[] args) {
string instruction = "340.00 10.00 taxing 1.00;" +
"27.00 120.00 takeoff 1.00;" +
"90.00 350.00 fly 30.00;" +
"180.00 400.00 cruise 230.00;" +
"50.00 320.00 fly 20.00;" +
"320.00 110.00 landing 3.00;"+
"120.00 10.00 taxing 3.00;";
Console.WriteLine(_interpreter.Interpret(instruction));
Console.ReadKey();
}
}
以上是调用方的代码,以下是这个案例的输出结果:
heading:340°,speed:10kn,movement:taxing on the runway,voyage:1km.
heading:27°,speed:120kn,movement:take off from the runway,voyage:1km.
heading:90°,speed:350kn,movement:flying in the sky,voyage:30km.
heading:180°,speed:400kn,movement:navigate a cruise,voyage:230km.
heading:50°,speed:320kn,movement:flying in the sky,voyage:20km.
heading:320°,speed:110kn,movement:landing on the runway,voyage:3km.
heading:120°,speed:10kn,movement:taxing on the runway,voyage:3km.
🚀总结
🔎1.优点
解释器模式(Interpreter Pattern)在某些情况下可以提供一些优点,尤其是在处理特定类型的问题时。以下是解释器模式的一些优点:
易于扩展新的语法规则:
- 解释器模式使得在语言中添加新的语法规则相对容易。通过创建新的终结符表达式和非终结符表达式,可以扩展解释器以支持新的语法构造,而不必修改现有的代码。
简化语法树的构建:
- 解释器模式可以将复杂的语法规则分解成简单的表达式,并使用这些表达式构建表达式树(Expression Tree)。这种分解和构建过程使得对语法的处理更加结构化和可管理。
易于实现语法解释:
- 对于特定领域的语言或领域特定语言(DSL),解释器模式提供了一种自然的方式来实现语法解释。它可以使领域专家更容易理解和维护领域特定的规则。
灵活性:
- 解释器模式允许在运行时动态修改解释器的行为,包括修改上下文中的变量值或替换解释器的一部分。这种灵活性对于处理动态生成的语法结构非常有用。
支持复杂条件和规则:
- 解释器模式适用于处理复杂的条件和规则,如查询语言、规则引擎等。它可以将这些复杂性抽象成可管理的表达式和子表达式。
模块化:
- 解释器模式将语法规则分解成各种表达式,从而使得每个表达式都可以独立实现和测试。这种模块化的设计有助于代码的可维护性和可扩展性。
尽管解释器模式具有这些优点,但它也有一些缺点,例如在处理大型表达式或频繁修改语法规则时可能会导致性能问题。因此,在选择是否使用解释器模式时,需要根据具体的问题和需求进行权衡和考虑。
🔎2.缺点
解释器模式(Interpreter Pattern)虽然在某些情况下具有优点,但也存在一些明显的缺点和限制,包括:
复杂性:
- 解释器模式的实现可能会变得相当复杂,特别是在处理复杂的语法规则和表达式时。每个语法规则都需要对应的终结符表达式和非终结符表达式,这可能导致类的数量迅速增加,使代码难以理解和维护。
性能开销:
- 解释器模式通常需要构建和遍历表达式树,这可能会导致性能开销较大。在处理大型表达式或频繁解释时,性能问题可能变得显著,因为解释器需要不断地递归和解释表达式。
不适用于所有问题:
- 解释器模式适用于特定类型的问题,如领域特定语言(DSL)的解释、规则引擎等,但不适用于所有问题。对于简单的问题或不需要灵活性的问题,引入解释器模式可能会过于复杂。
维护困难:
- 当语法规则发生变化或需要添加新的规则时,维护解释器模式的代码可能会变得困难。修改解释器模式的结构可能会导致需要修改大量的代码,同时确保不破坏现有功能也可能是挑战性的。
不利于性能优化:
- 由于解释器模式的特性,一些性能优化技巧(如编译成中间代码或使用 JIT 编译器)可能不易实现。这可能导致解释器模式在某些情况下性能不佳。
可能导致过度设计:
- 引入解释器模式可能会导致过度设计,特别是当应用程序中只有少数几个简单的语法规则时。在这种情况下,使用解释器模式可能会增加代码复杂性而没有实际的好处。
解释器模式是一种强大的设计模式,但它并不适用于所有情况。在使用解释器模式时,需要仔细权衡其优点和缺点,并确保其适用于特定的问题和需求。如果不需要处理复杂的语法解释或性能要求很高,可能有更简单和高效的解决方
🔎3.使用场景
解释器模式(Interpreter Pattern)适用于特定的场景和问题,通常在以下情况下可以考虑使用解释器模式:
领域特定语言(DSL)解释:
- 当需要构建一个领域特定语言(DSL)并解释执行DSL表达式时,解释器模式非常有用。DSL通常用于特定领域的问题领域,如数学表达式、查询语言等。
规则引擎:
- 在需要实现规则引擎的应用中,解释器模式可以用于解释规则,并基于规则执行相应的动作。例如,商业规则引擎、验证规则引擎等。
编程语言解释器:
- 开发自定义编程语言或脚本解释器时,可以使用解释器模式。它允许解释代码并执行相应的操作,类似于一些脚本语言的实现。
正则表达式解析:
- 正则表达式引擎通常是一个解释器,可以用解释器模式来实现对正则表达式的解析和匹配。
数学表达式求值:
- 当需要解析和计算数学表达式(如数学公式、算术表达式)时,解释器模式可以用于构建解释器,对表达式进行求值。
配置解析:
- 当需要解析配置文件或配置字符串,并根据解析的配置执行相应的操作时,解释器模式可以用于解释配置规则并执行相应的配置动作。
复杂的报表生成:
- 在需要根据特定规则生成复杂报表或文档时,解释器模式可以用于解释规则并生成相应的报表结构。
总的来说,解释器模式适用于需要解释、解析和执行特定语法规则或规则集的情况。它允许将语法规则表示为抽象语法树,并通过逐步解释树节点来执行相应的操作。使用解释器模式可以使得处理复杂的语法和规则变得更加灵活和可维释器模式(Interpreter Pattern)适用于以下场景:
语言解释器:当需要解释和执行特定语言或语法的表达式时,如编程语言解释器、查询语言解释器等。
数学表达式求值:例如,需要解析数学表达式并进行求值操作,解释器模式可用于构建表达式解释器。
正则表达式:在构建正则表达式引擎时,可以使用解释器模式来解释和匹配正则表达式。
配置解析:当需要解析配置文件或配置字符串,并根据解析的配置执行相应操作时,可以使用解释器模式。
规则引擎:构建规则引擎,根据特定规则进行操作,例如商业规则引擎、验证规则引擎等。
领域特定语言(DSL):构建DSL以解决特定领域的问题,使用解释器模式来解释DSL表达式。
复杂报表生成:生成复杂报表或文档,其中报表结构由特定规则决定,解释器模式可以解释这些规则并生成报表结构。
网络协议解释:当需要解释网络协议的数据包时,可以使用解释器模式解释协议的结构和内容。
解释器模式通过将语法规则表示为抽象语法树,然后通过解释器逐步解释树节点来执行相应操作,使得处理复杂的语法和规则变得更加灵活和可维护。
🚀感谢:给读者的一封信
亲爱的读者,
我在这篇文章中投入了大量的心血和时间,希望为您提供有价值的内容。这篇文章包含了深入的研究和个人经验,我相信这些信息对您非常有帮助。
如果您觉得这篇文章对您有所帮助,我诚恳地请求您考虑赞赏1元钱的支持。这个金额不会对您的财务状况造成负担,但它会对我继续创作高质量的内容产生积极的影响。
我之所以写这篇文章,是因为我热爱分享有用的知识和见解。您的支持将帮助我继续这个使命,也鼓励我花更多的时间和精力创作更多有价值的内容。
如果您愿意支持我的创作,请扫描下面二维码,您的支持将不胜感激。同时,如果您有任何反馈或建议,也欢迎与我分享。
再次感谢您的阅读和支持!
最诚挚的问候, “愚公搬代码”
- 点赞
- 收藏
- 关注作者
评论(0)