(精华)2020年8月29日 二十三种设计模式(十五)-解释器模式(Interpreter Pattern)

100 阅读5分钟

解释器模式(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.

优点

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

缺点

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

使用场景

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