細心!用心!耐心!

          吾非文人,乃市井一俗人也,讀百卷書,跨江河千里,故申城一游; 一兩滴辛酸,三四年學業,五六點粗墨,七八筆買賣,九十道人情。

          BlogJava 聯系 聚合 管理
            1 Posts :: 196 Stories :: 10 Comments :: 0 Trackbacks
          對於一個具有層次節點關係的問題來說,如果您要剖析每一個節點,您可以使用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 END
          PROGRAM 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 類別結構圖:
          Intrepreter

          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>* END
          public 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 | 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();
          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)模式等等,在這邊建議您看看那個範例,看看不同的設計模式之間如何組合且相互合作。
          posted on 2007-04-17 10:47 張金鵬 閱讀(319) 評論(0)  編輯  收藏 所屬分類: Behavioral 模式
          主站蜘蛛池模板: 玉门市| 额尔古纳市| 江城| 新津县| 吉林省| 京山县| 五华县| 安阳县| 安阳市| 诸暨市| 邯郸县| 中西区| 阳谷县| 孟连| 江西省| 双鸭山市| 西吉县| 弋阳县| 闻喜县| 广东省| 美姑县| 高碑店市| 东平县| 凤翔县| 介休市| 化德县| 合肥市| 利辛县| 汝州市| 崇义县| 苍溪县| 佛坪县| 云南省| 渝北区| 高州市| 长治县| 大厂| 县级市| 高密市| 延吉市| 安塞县|