当前位置: 代码迷 >> 综合 >> 解释器(Interpreter)模式
  详细解决方案

解释器(Interpreter)模式

热度:82   发布时间:2023-12-24 10:17:14.0

文章目录

  • 解释器(Interpreter)模式
    • 1. 意图
    • 2. 别名
    • 3. 动机
    • 4. 适用性
    • 5. 结构
    • 6. 参与者
    • 7. 协作
    • 8. 效果
    • 9. 实现
    • 10. 代码示例
    • 11. 已知应用
    • 12. 相关模式
    • 13. 设计原则口袋
    • 14. 参考文献

解释器(Interpreter)模式

隶属类别——类行为型模式


1. 意图

给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

2. 别名

3. 动机

如果一个特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构造一个解释器,该解释器通过解释这些句子来解决该问题。

例如,搜素匹配一个模式的字符串是一个常见问题。正则表达式是描述字符串模式的一个标准语言。与其为每一个的模式都构造一个特定的算法,不如使用一种通用的搜索算法来执行一个正则表达式,该正则表达式定义了待匹配字符串的集合。

解释器模式描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子。在下面的例子中,本设计模式描述了如何为正则表达式定义一个文法,如何表达一个特定的正则表达式,以及如何解释这个正则表达式。

考虑以下文法定义正则表达式:

expression ::= literal | alternation | sequence | repetition | '(' expression ')'
alternation ::= expression '|' expression
sequence ::= expression '&' expression
repetition ::= expression '*'
literal ::= 'a' | 'b' | 'c' | ... {'a' | 'b' | 'c' | ...}*

符号expression是开始符号,literal是定义简单字的终结符。

解释器模式使用类来表示每一条文法规则。在规则右边的符号是这些类的实例变量。上面的文法用五个类表示:一个抽象类RegularExpression和它四个子类LiteralExpression、AlternationExpression、SequenceExpression和RepetirionExpression后三个类定义的变量代表子表达式。

在这里插入图片描述

每个用这个文法定义的正则表达式都被表示为一个由这些类的实例构成的抽象语法树,例如,抽象语法树:

在这里插入图片描述

表示正则表达式:

raining & (dogs | cats) *

如果我们为RegularExpression的每一个子类都定义解释(Interpret)操作,那么就得到了为这些正则表达式的一个解释器。解释器将该表达式的上下文作为一个参数。上下文包含输入字符和关于面前它已有多少被匹配等信息。为匹配输入字符串的下一部分,每一个RegularExpression的子类都在当前上下文的基础上实现解释(interpret)操作,例如,

  • LiteralExpression将检查输入是否匹配它定义的字(literal)。
  • AlternationExpression将检查输入是否匹配它的任意一个选择项。
  • RepeatitionExpression将检查输入是否含有多个它所重复的表达式。
  • SequenceExpression将检查输入的表达式的顺序。

4. 适用性

当有一个语言需要解释执行,并且你可将该语言中的句子表示为一个抽象语法树时,可使用解释器模式。而当存在以下情况时该模式效果最好。

  • 文法很简单 对于复杂的文法,文法的类层次变得庞大而无法管理。此时语法分析程序生成器这样的工具是更好的选择。它们无需构建抽象语法树即可解释表达式,这样可以节省空间而且还可能节省时间。
  • 效率不是一个关键问题 最高效的解释器通常不是通过直接解释语法分析树实现的,而是首先将它们转换成另一种形式。例如,正则表达式通常被转换成状态机,但即使在这种情况下,转换器仍可用解释器模式实现,该模式仍是可用的。

5. 结构

在这里插入图片描述

6. 参与者

  • AbstractExpression(抽象表达式,如RegularExpression)

    • 声明一个抽象的解释操作,这个接口被抽象语法树中所有的节点所共享。
  • TerminalExpression(终结符表达式, 如LiteralExpression)

    • 实现与文法中的终极符相关联的解释操作。
    • 一个句子中的每个终结符需要该类的一个实例。
  • NonterminalExpression(非终结符表达式, 如AlternationExpression, RepetitionExpression,SequenceExpression)

    • 对文法中的每一条规则R ::=R1R2…Rn都需要一个NonterminalExpression类。

    • 为从R1到Rn的每个字符都维护一个AbstractExpression类型的实例变量。

    • 为文法中的非终结符的实现解释(Interpret)操作,解释(Interpret)一般要递归地调用表示R1

      到Rn的那些对象的解释操作。

  • Context(上下文)

    • 包含解释器之外的一些全局信息。
  • Client(客户)

    • 构建(或被给定)表示该文法定义的语言中一个特地的句子的抽象语法树。该抽象语法树由NonterminalExpression和TerminalExpression的实例装配而成。
    • 调用解释操作

7. 协作

  • Client构建(或被给定)一个句子,它是有NonterminalExpression和TERMINALeXPRESSION的实例装配成的一个抽象语法树,然后初始化上下文并调用解释操作。
  • 每一飞终结符表达式节点定义相应子表达式的解释操作。而各终结符表达式的解释操作构成了递归的基础。
  • 每一节点的解释操作用上下文来存储和访问解释器的状态。

8. 效果

解释器模式有下列的优点

  • 1) 易于改变和扩展文法 因为该模式使用类来表达文法规则。你可以使用1继承来改变或扩展该文法。已有的表达式可被增量式的改变,而新的表达式可以定义为旧表达式的变体。
  • 2) 易于实现文法 定义抽象语法树种的各个节点的类的实现大体类似。这些类易于直接编写,通常他们也可用一个编译器或语法分析程序器自动生成。
  • 3) 增加了新的解释器表达式的方式 解释器模式使得实现新表达式"计算"变得容易。例如,你可以在表达式类上定义一个新的操作以支持优美打印或者表达式的类型检查。如果你经常创新的解释表达式的方式,那么可以考虑使用Visitor模式来避免修改这些代表文法的类。

缺点:

  • 1)复杂的文法难以维护 解释器模式为文法中的每一条规则至少定义一个类(使用BNF(巴科斯范式)定义的文法规则需要更多的类),因此包含许多规则的文法可能难以管理和文虎。可应用其他的设计模式来缓解这一问题。但当文法非常复杂是,其他的技术如语法分析程序或者编译器生成器更为合适。

9. 实现

Interpreter和Composite模式在实现的上有许多相同的地方。下面是Interpreter所要考虑的一些特殊问题:

  • 1)创建抽象语法树 解释器模式并未解释如何创建一个抽象的语法树。换言之,它不涉及语法分析。抽象语法树可用一个表驱动的语法分析树可用一个表驱动的语法分析程序来生成, 也可用手写的(通常为递归下降法)语法分析程序创建。或者直接由Client提供。

  • 2) 定义解释操作 并不一定会要在表达式类中定义解释操作。如果经常要创建一种新的解释器,那么使用Visitor模式将解释放入一个独立的"访问者"对象更好一些。例如,一个程序设计语言会有许多在抽象语法树上的操作,比如类型检查、优化、代码生成、等等。恰当的做法是使用一个访问者以避免在每一个类都定义这个操作。 how ?

    1. 与Flyweight模式共享终结符 在一个文法中,一个句子可能出现多个出现同一个终结符。此时最好共享那个符号的单个拷贝。计算机程序的文法是一个很好的例子——每个程序变量在整个代码中将会出现多次。在动机的例子中,一个句子中终结符dog(由LiteralExpression类描述)也可能多次。

    终结节点通常不存储关于它们在抽象语法树中位置的信息。在解释过程中,任何它们所需要的上下文信息都由父节点传递给它们。因此在共享的(状态)状态和传入的(外部的)状态区分得很明确,这就用到了Flyweight模式。

    例如,dog literalExpression的每一个实例接受一个包含目前已匹配子串信息的上下文。且每一个这样的LiteralExpression在它的解释操作中做同样一件事(它检查输入的下一部分是否包含一个dog)无论该实例出现在语法树的哪个位置。

10. 代码示例

本次想要设置的一个解释器模式时从一些String中解释一些pets的jump和bark动作。

首先是Context——PetContext.java

public class PetContext {
    public void barkPet(Pet pet) {
    pet.bark();;}public void jumpPet(Pet pet) {
    pet.jump();}
}

然后是AbstractExpression——Expression.java

public interface Expression {
    void interpret(PetContext pc);
}

两个TerMinalExpressin——BarkExpression.java & JumpExpression.java

BarkExpression.java

public class BarkExpression implements Expression {
    private Pet pet;BarkExpression(Pet pet) {
    this.pet = pet;}@Overridepublic void interpret(PetContext pc) {
    pc.barkPet(this.pet);}
}

JumpExpression.java

public class JumpExpression implements Expression {
    private Pet pet;JumpExpression(Pet pet){
    this.pet = pet;}@Overridepublic void interpret(PetContext pc) {
    pc.jumpPet(this.pet);}
}

Pet与其子类的层次结构的UML图如下所示

在这里插入图片描述

由于篇幅问题,这里只放一下Pet类的实现与两个接口Jumpable & Barkable 和一个Pet子类Cat类

Pet.java

public class Pet implements Barkable, Jumpable {
    public Pet() {
     super(); }@Overridepublic void bark() {
    System.out.println(this + " bark ");}@Overridepublic void jump() {
    System.out.println(this + " jump ");}@Overridepublic String toString() {
    return "Pet";}
}

Jumpable.java

public interface Jumpable {
    void jump();
}

Barkable.java

public interface Barkable {
    void bark();
}

Cat.java

public class Cat extends Pet {
    public Cat() {
     super(); try {
    Thread.sleep(5000);} catch (InterruptedException e) {
    System.err.println("Create cat failed");e.printStackTrace(System.err);}}@Overridepublic String toString() {
    return "Cat";}}

这里Cat我特意在构造方法中设置了5秒的Delay,来体现接下来要使用的Flyweight的好处。

接下来是Client——InterpreterClient.java

public class InterpreterClient {
    private PetContext pc;private Expression exp = null;InterpreterClient(PetContext pc) {
    this.pc = pc;}void interpreter(String msg) {
    msg = msg.toLowerCase();Pet pet = PetFactory.createPet(msg);if (pet != null) {
     if (msg.contains("jump") || msg.contains("jumping")) {
    exp = new JumpExpression(pet);} else if (msg.contains("bark") || msg.contains("barking")) {
    exp = new BarkExpression(pet);}exp.interpret(pc);} else {
    System.out.println("This sentence can't be interpreted");};		}public static void main(String[] args) throws Exception {
    // TODO Auto-generated method stubString str1 = "I just want to see a Dog barking";String str2 = "Mouse is cute while it's barking";String str3 = "Cat can jump so high which I can't image";String str4 = "I don't want to feed a Pet, because it's barking is noice.";String str5 = "Look! the Pug could is jumping across the big stone";String str6 = "Do you like jumping cat?";String[] strArray = {
    str1, str2, str3, str4, str5, str6};List<String> strList = new ArrayList<>(Arrays.asList(strArray));for (int i = 0; i < 10; i++) {
    strList.add(str6);}InterpreterClient ic = new InterpreterClient(new PetContext());for (String str : strList) {
    ic.interpreter(str);Thread.sleep(1000);}}}

我这里使用了Factory模式用来生成创建宠物。

PetFactory.java

public class PetFactory {
    private static Map<PetType, Pet> petFlyweightPool = new HashMap<>();static Pet createPet(String type) {
    type = type.toLowerCase();PetType petType = null;Pet pet = null;if (type.contains("pet")) {
    petType = PetType.PET;pet = petFlyweightPool.get(petType);if (pet == null) {
    pet = new Pet();}	petFlyweightPool.put(petType, pet);} else if (type.contains("dog")) {
    petType = PetType.DOG;pet = petFlyweightPool.get(petType);if (pet == null) {
    pet = new Dog();}petFlyweightPool.put(petType, pet);} else if (type.contains("cat")) {
    petType = PetType.CAT;pet = petFlyweightPool.get(petType);if (pet == null) {
    pet = new Cat();}petFlyweightPool.put(petType, pet);} else if (type.contains("mouse")) {
    petType = PetType.MOUSE;pet = petFlyweightPool.get(petType);if (pet == null) {
    pet = new Mouse();}petFlyweightPool.put(petType, pet);} else if (type.contains("cymric")) {
    petType = PetType.CYCMIC;pet = petFlyweightPool.get(petType);if (pet == null) {
    pet = new Cymric();}petFlyweightPool.put(petType, pet);} else if (type.contains("pug")) {
    petType = PetType.PUG;pet = petFlyweightPool.get(petType);if (pet == null) {
    pet = new Pug();}petFlyweightPool.put(petType, pet);} else if (type.contains("hamster")) {
    petType = PetType.HAMSTER;pet = petFlyweightPool.get(petType);if (pet == null) {
    pet = new Hamster();}petFlyweightPool.put(petType, pet);}return pet;}
}

值得一提的是,这里还使用了实现一节中的提到的Flyweight模式来共享终结符,当需要解释的对象很多的时候,可以利用共享的Flyweight,从而节省内存。

上述的枚举类PetType.java

public enum PetType {
    PET,DOG,CAT,MOUSE,CYCMIC,PUG,HAMSTER;
}

最后的测试结果

Dog bark 
Mouse bark 
Cat jump 
Pet bark 
Pug jump 
Cat jump 
Cat jump 
Cat jump 
Cat jump 
Cat jump 
Cat jump 
Cat jump 
Cat jump 
Cat jump 
Cat jump 
Cat jump 

可以看到理想的结果,捕捉到各种Pet的jump和bark动作。

最后放上解释器模式类的UML图:

在这里插入图片描述

11. 已知应用

解释器模式在使用面向对象语言实现的编译器中得到了广泛应用,如Smalltalk编译器,SPECTalk使用该模式解释输入文件格式的描述。QOCA约束——求解工具使用它对约束进行计算。

在最宽泛的概念下(即,分布在基于Composite模式的类层次上的一种操作),几乎每个使用组合模式的系统也使用了解释器模式。但一般只有在用一个类层次来定义某个语言时,才强调使用使用解释器模式。

12. 相关模式

  • Composite:抽象语法树是一个Composite模式的实例
  • Flyweight:说明了如何在抽象语法树中共享终结符
  • Iterator: 解释器可用一个迭代器遍历该结构
  • Visitor: 可用来在一个类中维持抽象语法树种的各节点的行为。

13. 设计原则口袋

  • 封装变化
  • 针对接口编程,不针对实现编程
  • 类应该对扩展开放,对修改关闭
  • 为交互对象间的松耦合设计而努力
  • 依赖抽象,不要依赖具体类
  • 多用组合,少用继承
  • 只和密友交谈
  • 好莱坞原则——别来找我,我会来找你
  • 单一责任原则——类应该只有一个改变的理由

14. 参考文献

《设计模式:可复用型面向对象软件的基础》

《HeadFirst设计模式》

  相关解决方案