写点什么

【愚公系列】2022 年 05 月 二十三种设计模式 (十五)- 解释器模式 (Interpreter Pattern)

作者:愚公搬代码
  • 2022 年 5 月 15 日
  • 本文字数:3667 字

    阅读完需:约 12 分钟

前言

设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。

一、解释器模式(Interpreter Pattern)

解释器模式属于行为型模式,给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。


解释器模式提供了评估语言的语法或表达式的方式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被广泛地应用在 SQL 解析、符号处理引擎等领域。

二、使用步骤

角色

1、抽象表达式(Expression)


声明一个所有的具体表达式角色都需要实现的抽象接口。这个接口一般是一个 Interpret()方法,称做解释操作;


2、终结符表达式(Terminal Expression)


实现了抽象表达式角色所要求的接口,一般是 Interpret()方法;文法中的每一个终结符都有一个具体终结表达式与之相对应。比如有一个简单的公式 R=R1+R2,在里面 R1 和 R2 就是终结符,对应的解析 R1 和 R2 的解释器就是终结符表达式;


3、非终结符表达式(Nonterminal Expression)


文法中的每一条规则都需要一个具体的非终结符表达式,非终结符表达式一般是文法中的运算符或者其他关键字,比如公式 R=R1+R2 中,“+就是非终结符,解析“+”的解释器就是一个非终结符表达式;


4、环境(Context)


这个角色的任务一般是用来存放文法中各个终结符所对应的具体值,比如 R=R1+R2,我们给 R1 赋值 100,给 R2 赋值 200。这些信息需要存放到环境角色中,很多情况下我们使用 Map 来充当环境角色就足够了。

示例


命名空间 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.
复制代码


<hr style=" border:solid; width:100px; height:1px;" color=#000000 size=1">

总结

优点

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

缺点

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

使用场景

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

发布于: 刚刚阅读数: 2
用户头像

还未添加个人签名 2022.03.01 加入

该博客包括:.NET、Java、前端、IOS、Android、鸿蒙、Linux、物联网、网络安全、python、大数据等相关使用及进阶知识。查看博客过程中,如有任何问题,皆可随时沟通。

评论

发布
暂无评论
【愚公系列】2022年05月 二十三种设计模式(十五)-解释器模式(Interpreter Pattern)_5月月更_愚公搬代码_InfoQ写作社区