设计模式之(二十)解释器模式Interpreter
2011-08-09 16:56
621 查看
对于一个具有层次节点关系的问题来说,如果您要剖析每一个节点,您可以使用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
这个程式要印出:
您也可以任意的组合程式,例如:
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
ProgramNode.java
CommandListNode.java
CommandNode.java
RepeatCommandNode.java
PrimitiveCommandNode.java
Context.java
Main.java
假设您的程式是这样写的:
program.txt
则执行Intrepreter程式之后会是:
这个范例程式基本上已经显示了直译器模式的工作原理,如何让程式直译之后能够工作,这待会再示范,先来看一下Intrepreter模式的 UML 类别结构图:
TerminalExpression就像我们的primitive command,再剖析下去已经没有子节点了,而NonterminalExpression就像是repeat command,注意到其中也使用了组合模式,就如之前所说的,组合模式让可以递回的组合句子为更复杂的语句。
您已经会剖析句子了,接下来要如何让这个直译器真正工作,虽然程式中使用toString()来表示每一个节点的剖析结果,但事实上,这个程式也已经说明 了如何让剖析的结果真正运作了,既然已经记录好剖析之后的语句顺序了,只要由上而下追踪剖析结果,就一定可以执行到 primitive command,且顺序符合自订的程式原始码的需求,这只要将toString()改为execute(),并作一些转发与重复执行的修改就可以了,直接
来看程式会比较容易理解:
INode.java
ProgramNode.java
CommandListNode.java
CommandNode.java
PrimitiveCommandNode.java
RepeatCommandNode.java
Context.java
Main.java
假设您的直译程式稿是这么撰写的:
program.txt
则程式执行的结果就是:
Design Patterns于Java语言之实习应用 第23章的范例中,可以让您依指令稿直译,画出任何的图案,让范例结合了工厂(Factory)模式、外观(Facade)模式等等,在这边建议您看看那个范例,看看不同的设计模式之间如何组合且相互合作。
转自http://www.riabook.cn/doc/designpattern/InterpreterPattern.htm
举个例子来说明好了,先说明的是,这个例子是改写自 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"]]]] |
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 ------------------------------ |
转自http://www.riabook.cn/doc/designpattern/InterpreterPattern.htm
相关文章推荐
- 设计模式(二十)解释器模式(Interpreter)-行为型
- 设计模式之(二十)解释器模式Interpreter
- 设计模式(二十)之设计模式之 Interpreter(解释器)
- 【设计模式 - 15】之解释器模式(Interpreter)
- Java设计模式菜鸟系列(二十)解释器模式建模与实现
- 【设计模式 java】——解释器模式(Interpreter)
- 设计模式(15)-行为型-解释器模式(Interpreter)
- 设计模式袖珍版 连续转载之 - Interpreter(解释器)
- php设计模式 Interpreter(解释器模式)
- java设计模式23——解释器模式(Interpreter)
- JAVA设计模式十五:--Interpreter(解释器模式)
- 设计模式之解释器模式(Interpreter)
- C#面向对象模式设计第十六讲:Interpreter 解释器模式(行为型模式)
- 设计模式-行为设计模式:解释器模式 Interpreter
- 步步为营 .NET 设计模式学习笔记 二十三、Interpreter(解释器模式)
- 设计模式--解释器模式(Interpreter)
- 23种设计模式之解释器模式(Interpreter)
- [设计模式]解释器模式(Interpreter)
- 设计模式学习笔记二十:解释器模式
- Java开发中的23种设计模式详解----解释器模式(Interpreter)