设计模式之(二十)解释器模式Interpreter

来源:互联网 发布:数据库未打开怎么解决 编辑:程序博客网 时间:2024/05/16 05:54
对于一个具有层次节点关系的问题来说,如果您要剖析每一个节点,您可以使用Interpreter模式,直译器模式有些类似演算法中的个别击破方式,对每一个父节点我们剖析出其子节点组合,然而交给子节点剖析物件继续剖析,直到剖析至终端节点为止。

举个例子来说明好了,先说明的是,这个例子是改写自 Design Patterns于Java语言之实习应用 第23章的范例,我将之更简化了,以让大家将焦点能集中在如何使用Interpreter模式,以及如何实用。

假设您要实作一个Interpreter,这个Interpreter可以直译您文字档中的程式,并依您自订的程式文法来执行程式,几个简单的程式如下:
PROGRAM 
    PRINT dog SPACE 
    PRINT is SPACE 
    PRINT an SPACE 
    PRINT animai 
END 
 
您的这式程个会印出"dog is an animal"的文字,再来一个例子是:
PROGRAM 
    REPEAT 2 
        LINEBREAK 
        PRINT dog 
        BREAK 
    END 
END
 

这个程式要印出:
------------------------------ 
 dog 
------------------------------ 
 dog

您也可以任意的组合程式,例如:
PROGRAM 
    PRINT begin 
    BREAK 
    REPEAT 3 
        REPEAT 2 
            PRINT dog SPACE 
            PRINT is SPACE 
            PRINT a SPACE 
            PRINT animal 
            BREAK 
        END 
    END 
END
 

这个程式中的几个关键字是PROGRAM、PRINT、SPACE、BREAK、LINEBREAK、REPEAT、END, PROGRAM是表示程式开始,以END作结,PRINT可以印出一个无空白的字串,SPACE印出一个空白,BREAK是换行,而LINEBREAK是 画一个直线并换行,REPEAT是回圈指令,可以指定回圈次数,以END作结。

观察程式,可以制定出以下的文法,如下:
<program> ::= PROGRAM <command list> 
<command list> ::= <command>* END 
<command> ::= <repeat command> | <primitive command> 
<repeat command> ::= REPEAT <number> <command list> 
<primitive command> ::= PRINT <string> 
                         | BREAK | SPACE | LINEBREAK
 

程式文法制定需要对程式进行语句分析与定义,在这边并不讨论这个课题,在程式中,command节点由primitive或repeat两个节点任意组 合,一个command list节点则是零个以上的command节点组合而成,其中repeat还可以组合command list节点,这是组合模式的应用,可以在程式中组合巢状回圈。

在直译程式时,以读到PROGRAM作为开始节点,接下来我们剖析程式为command list 节点,并将它们丢给专门剖析command list的物件继续剖析,这个物件将之分析,看是不是有repeat command或primitive command节点,如果有就再往下交由专属物件进行剖析,如此层层剥开,并由专属物件负责剖析工作。

Interpreter模式的基本观念就如上所示,先来看看如何以程式实现剖析的过程,下面这个程式会剖析您的程式,并将程式加上对应的括号来将同一个区块组合起来,以表示它完成剖析之后的结果:
  • INode.java
public interface INode {     public void parse(Context context); } 

  • ProgramNode.java
// <program> ::= PROGRAM <command list> public class ProgramNode implements INode {     private INode commandListNode;     public void parse(Context context) {         context.skipToken("PROGRAM");         commandListNode = new CommandListNode();         commandListNode.parse(context);     }     public String toString() {         return "[PROGRAM " + commandListNode + "]";     } }  

  • CommandListNode.java
import java.util.Vector; // <command list> ::= <command>* END public class CommandListNode implements INode {     private Vector list = new Vector();    public void parse(Context context) {         while (true) {             if (context.currentToken() == null) {                 System.err.println("Missing 'END'");                  break;             } else if (                    context.currentToken().equals("END")) {                 context.skipToken("END");                 break;             } else {                 INode commandNode = new CommandNode();                 commandNode.parse(context);                 list.add(commandNode);             }         }     }    public String toString() {         return "" + list;     } }  

  • CommandNode.java
// <command> ::= <repeat command> | <primitive command> public class CommandNode implements INode {     private INode node;    public void parse(Context context) {         if (context.currentToken().equals("REPEAT")) {             node = new RepeatCommandNode();             node.parse(context);         } else {             node = new PrimitiveCommandNode();             node.parse(context);         }     }    public String toString() {         return node.toString();     } }  

  • RepeatCommandNode.java
public class RepeatCommandNode implements INode {     private int number;     private INode commandListNode;     public void parse(Context context) {         context.skipToken("REPEAT");         number = context.currentNumber();         context.nextToken();         commandListNode = new CommandListNode();         commandListNode.parse(context);     }    public String toString() {        return "[REPEAT " + number + " "                   + commandListNode + "]";     } }

  • PrimitiveCommandNode.java
// <primitive command> ::= PRINT <string> //                           | SPACE | BREAK | LINEBREAK public class PrimitiveCommandNode implements INode {    private String name;    private String text;    public void parse(Context context) {         name = context.currentToken();         context.skipToken(name);         if (!name.equals("PRINT") && !name.equals("BREAK")              && !name.equals("LINEBREAK")              && !name.equals("SPACE")) {             System.err.println("Undefined Command");         }        if (name.equals("PRINT")) {             text = context.currentToken();             name += text;             context.nextToken();         }     }    public String toString() {         return name;     } }  

  • Context.java
import java.util.*; public class Context {     private StringTokenizer tokenizer;     private String currentToken;     public Context(String text) {         tokenizer = new StringTokenizer(text);         nextToken();     }     public String nextToken() {         if (tokenizer.hasMoreTokens()) {             currentToken = tokenizer.nextToken();         } else {             currentToken = null;         }         return currentToken;     }     public String currentToken() {         return currentToken;     }     public void skipToken(String token) {         if (!token.equals(currentToken)) {             System.err.println("Warning: " + token +                           " is expected, but " +                           currentToken + " is found.");         }         nextToken();     }     public int currentNumber() {         int number = 0;         try {             number = Integer.parseInt(currentToken);         } catch (NumberFormatException e) {             System.err.println("Warning: " + e);         }         return number;     } }  

  • Main.java
import java.util.*; import java.io.*;public class Main {     public static void main(String[] args) {         try {             BufferedReader reader = new                   BufferedReader(new FileReader(args[0]));             String text;             while ((text = reader.readLine()) != null) {                 System.out.println("text = \"" +                                        text + "\"");                 INode node = new ProgramNode();                 node.parse(new Context(text));                 System.out.println("node = " + node);             }         }         catch (ArrayIndexOutOfBoundsException e) {             System.err.println(                   "Usage: java Main yourprogram.txt");         }         catch (Exception e) {             e.printStackTrace();         }     } } 

假设您的程式是这样写的:
  • program.txt
PROGRAM PRINT xxx ENDPROGRAM REPEAT 4 PRINT xxx END END PROGRAM REPEAT 4 PRINT xxx PRINT "yyy" END END

则执行Intrepreter程式之后会是:
 $ java Main program.txt 
 text = "PROGRAM PRINT xxx END" 
 node = [PROGRAM [PRINTxxx]] 

 text = "PROGRAM REPEAT 4 PRINT xxx END END" 
 node = [PROGRAM [[REPEAT 4 [PRINTxxx]]]] 

 text = "PROGRAM REPEAT 4 PRINT xxx PRINT "yyy" END END" 
 node = [PROGRAM [[REPEAT 4 [PRINTxxx, PRINT"yyy"]]]]

这个范例程式基本上已经显示了直译器模式的工作原理,如何让程式直译之后能够工作,这待会再示范,先来看一下Intrepreter模式的 UML 类别结构图: 


TerminalExpression就像我们的primitive command,再剖析下去已经没有子节点了,而NonterminalExpression就像是repeat command,注意到其中也使用了组合模式,就如之前所说的,组合模式让可以递回的组合句子为更复杂的语句。

您已经会剖析句子了,接下来要如何让这个直译器真正工作,虽然程式中使用toString()来表示每一个节点的剖析结果,但事实上,这个程式也已经说明 了如何让剖析的结果真正运作了,既然已经记录好剖析之后的语句顺序了,只要由上而下追踪剖析结果,就一定可以执行到 primitive command,且顺序符合自订的程式原始码的需求,这只要将toString()改为execute(),并作一些转发与重复执行的修改就可以了,直接 来看程式会比较容易理解:
  • INode.java
public interface INode {    public void parse(Context context);    public void execute();} 

  • ProgramNode.java
// <program> ::= PROGRAM <command list>public class ProgramNode implements INode {    private INode commandListNode;    public void parse(Context context) {        context.skipToken("PROGRAM");        commandListNode = new CommandListNode();        commandListNode.parse(context);    }    public void execute() {        commandListNode.execute();    }    public String toString() {        return "[PROGRAM " + commandListNode + "]";    }} 

  • CommandListNode.java
import java.util.*;    // <command list> ::= <command>* ENDpublic class CommandListNode implements INode {    private Vector list = new Vector();    private INode commandNode;    public void parse(Context context) {        while (true) {            if (context.currentToken() == null) {                System.err.println("Missing 'END'");                break;            } else if(context.currentToken().equals("END")) {                context.skipToken("END");                break;            } else {                commandNode = new CommandNode();                commandNode.parse(context);                list.add(commandNode);            }        }    }    public void execute() {        Iterator it = list.iterator();        while (it.hasNext()) {            ((CommandNode)it.next()).execute();        }    }    public String toString() {        return "" + list;   }} 

  • CommandNode.java
// <command> ::= <repeat command> | <primitive command>public class CommandNode implements INode {    private INode node;    public void parse(Context context) {        if (context.currentToken().equals("REPEAT")) {            node = new RepeatCommandNode();            node.parse(context);        } else {            node = new PrimitiveCommandNode();            node.parse(context);        }    }    public void execute() {        node.execute();    }    public String toString() {        return node.toString();    }} 

  • PrimitiveCommandNode.java
// <primitive command> ::= PRINT <string> //                           | SPACE | BREAK | LINEBREAKpublic class PrimitiveCommandNode implements INode {    private String name;    private String text;    public void parse(Context context) {        name = context.currentToken();        context.skipToken(name);        if (!name.equals("PRINT") && !name.equals("BREAK")                            && !name.equals("LINEBREAK")                            && !name.equals("SPACE")) {            System.err.println("Undefined Command");        }        if (name.equals("PRINT")) {            text = context.currentToken();            context.nextToken();        }    }     public void execute() {        if(name.equals("PRINT"))            System.out.print(text);        else if(name.equals("SPACE"))            System.out.print(" ");        else if(name.equals("BREAK"))            System.out.println();        else if(name.equals("LINEBREAK"))            System.out.println(                  "\n------------------------------");    }    public String toString() {        return name;    }} 

  • RepeatCommandNode.java
public class RepeatCommandNode implements INode {    private int number;    private INode commandListNode;    public void parse(Context context) {        context.skipToken("REPEAT");        number = context.currentNumber();        context.nextToken();        commandListNode = new CommandListNode();        commandListNode.parse(context);    }    public void execute() {        for(int i = 0; i < number; i++)            commandListNode.execute();    }    public String toString() {        return "[REPEAT " + number + " " +                              commandListNode + "]";    }} 

  • Context.java
import java.util.*;public class Context {    private StringTokenizer tokenizer;    private String currentToken;    public Context(String text) {        tokenizer = new StringTokenizer(text);        nextToken();    }    public String nextToken() {        if (tokenizer.hasMoreTokens()) {            currentToken = tokenizer.nextToken();        } else {            currentToken = null;        }        return currentToken;    }    public String currentToken() {        return currentToken;    }    public void skipToken(String token) {        if (!token.equals(currentToken)) {            System.err.println("Warning: " + token +                            " is expected, but " +                            currentToken + " is found.");        }        nextToken();    }    public int currentNumber() {        int number = 0;        try {            number = Integer.parseInt(currentToken);        } catch (NumberFormatException e) {            System.err.println("Warning: " + e);        }        return number;    }} 

  • Main.java
import java.util.*;import java.io.*;public class Main {    public static void main(String[] args) {        try {            BufferedReader reader = new BufferedReader(                               new FileReader(args[0]));            String text;            while ((text = reader.readLine()) != null) {                System.out.println("text = \"" + text                                       + "\"");                INode node = new ProgramNode();                node.parse(new Context(text));                node.execute();            }        }        catch (ArrayIndexOutOfBoundsException e) {            System.err.println(                 "Useage: java Main yourprogram.txt");        }        catch (Exception e) {            e.printStackTrace();        }    }}

假设您的直译程式稿是这么撰写的: 
  • program.txt
PROGRAM REPEAT 4 LINEBREAK PRINT justin SPACE PRINT momor LINEBREAK END END

则程式执行的结果就是: 
  $ java Main program.txt 
 text = "PROGRAM REPEAT 4 LINEBREAK PRINT justin SPACE 
         PRINT momor LINEBREAK END END" 
 ------------------------------ 
 justin momor 
 ------------------------------ 

 ------------------------------ 
 justin momor 
 ------------------------------ 

 ------------------------------ 
 justin momor 
 ------------------------------ 

 ------------------------------ 
 justin momor 
 ------------------------------ 

Design Patterns于Java语言之实习应用 第23章的范例中,可以让您依指令稿直译,画出任何的图案,让范例结合了工厂(Factory)模式、外观(Facade)模式等等,在这边建议您看看那个范例,看看不同的设计模式之间如何组合且相互合作。
转自http://www.riabook.cn/doc/designpattern/InterpreterPattern.htm
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 主页被360串改怎么办? 入职体检查乙肝怎么办 入职体检两对半怎么办? 入职体检肝功能异常怎么办 有乙肝怀孕建卡怎么办 重修的课有冲突怎么办 苹果7硬件坏了怎么办 发票记账联丢了怎么办 作废的发票没了怎么办 键盘esc键坏了怎么办 cad中esc键失灵怎么办 出家人老了病了怎么办 宜家吸盘吸不住怎么办 车内吸盘吸不住怎么办 水里吸盘吸不住怎么办 摩托车脱审4年怎么办 摩托车8年没年检怎么办 摩托车4年没年检怎么办 38度5算高烧吗怎么办 刚发现怀孕不想要怎么办 被蜥蜴咬出血了怎么办 去韩国打工签证怎么办多么钱 厕所堵了怎么办有妙招 蹲式厕所老是堵怎么办 下蹲式厕所堵了怎么办 蹲的厕所堵住了怎么办 厕所通了又堵怎么办 蹲式厕所经常堵怎么办 厕所下水管堵了怎么办 厕所堵了水满了怎么办 拉屎把厕所堵了怎么办 厕所堵了不下水怎么办 坐厕所堵了怎么办妙招 火车上丢了东西怎么办 网购的东西丢了怎么办 在酒店丢了东西怎么办 我好懒不想工作怎么办 被宠物刺猬咬了怎么办 被老鼠咬了怎么办打针 手指被老鼠咬了怎么办 孕妇被老鼠咬了怎么办