目录

搜索

内容详情

投稿

1 2

18.5 再谈Context的作用

       在解释器模式中,环境类Context用于存储解释器之外的一些全局信息,它通常作为参数被传递到所有表达式的解释方法interpret()中,可以在Context对象中存储和访问表达式解释器的状态,向表达式解释器提供一些全局的、公共的数据,此外还可以在Context中增加一些所有表达式解释器都共有的功能,减轻解释器的职责。

       在上面的机器人控制程序实例中,我们省略了环境类角色,下面再通过一个简单实例来说明环境类的用途:

       Sunny软件公司开发了一套简单的基于字符界面的格式化指令,可以根据输入的指令在字符界面中输出一些格式化内容,例如输入“LOOP 2 PRINT杨过 SPACE SPACE PRINT 小龙女 BREAK END PRINT郭靖 SPACE SPACE PRINT 黄蓉”,将输出如下结果:

杨过     小龙女杨过     小龙女郭靖     黄蓉

       其中关键词LOOP表示“循环”,后面的数字表示循环次数;PRINT表示“打印”,后面的字符串表示打印的内容;SPACE表示“空格”;BREAK表示“换行”;END表示“循环结束”。每一个关键词对应一条命令,计算机程序将根据关键词执行相应的处理操作。

       现使用解释器模式设计并实现该格式化指令的解释,对指令进行分析并调用相应的操作执行指令中每一条命令。

       Sunny软件公司开发人员通过分析,根据该格式化指令中句子的组成,定义了如下文法规则:

expression ::= command* //表达式,一个表达式包含多条命令

command ::= loop | primitive //语句命令

loop ::= 'loopnumber' expression  'end' //循环命令,其中number为自然数

primitive ::= 'printstring'  | 'space' | 'break' //基本命令,其中string为字符串

       根据以上文法规则,通过进一步分析,绘制如图18-6所示结构图:

18-6    格式化指令结构图

       在图18-6中,Context充当环境角色,Node充当抽象表达式角色,ExpressionNodeCommandNodeLoopCommandNode充当非终结符表达式角色,PrimitiveCommandNode充当终结符表达式角色。完整代码如下所示:

[java] view plaincopy

  1. import java.util.*;  

  2.   

  3. //环境类:用于存储和操作需要解释的语句,在本实例中每一个需要解释的单词可以称为一个动作标记(Action Token)或命令  

  4. class Context {  

  5.     private StringTokenizer tokenizer; //StringTokenizer类,用于将字符串分解为更小的字符串标记(Token),默认情况下以空格作为分隔符  

  6.     private String currentToken; //当前字符串标记  

  7.       

  8.     public Context(String text) {  

  9.         tokenizer = new StringTokenizer(text); //通过传入的指令字符串创建StringTokenizer对象  

  10.         nextToken();  

  11.     }  

  12.       

  13.     //返回下一个标记  

  14.     public String nextToken() {  

  15.         if (tokenizer.hasMoreTokens()) {  

  16.             currentToken = tokenizer.nextToken();  

  17.         }  

  18.         else {  

  19.             currentToken = null;  

  20.         }  

  21.         return currentToken;  

  22.     }  

  23.       

  24.     //返回当前的标记  

  25.     public String currentToken() {  

  26.         return currentToken;  

  27.     }  

  28.       

  29.     //跳过一个标记  

  30.     public void skipToken(String token) {  

  31.         if (!token.equals(currentToken)) {  

  32.             System.err.println("错误提示:" + currentToken + "解释错误!");  

  33.             }  

  34.         nextToken();  

  35.     }  

  36.       

  37.     //如果当前的标记是一个数字,则返回对应的数值  

  38.     public int currentNumber() {  

  39.         int number = 0;  

  40.         try{  

  41.             number = Integer.parseInt(currentToken); //将字符串转换为整数  

  42.         }  

  43.         catch(NumberFormatException e) {  

  44.             System.err.println("错误提示:" + e);  

  45.         }  

  46.         return number;  

  47.     }  

  48. }  

  49.   

  50. //抽象节点类:抽象表达式  

  51. abstract class Node {  

  52.     public abstract void interpret(Context text); //声明一个方法用于解释语句  

  53.     public abstract void execute(); //声明一个方法用于执行标记对应的命令  

  54. }  

  55.   

  56. //表达式节点类:非终结符表达式  

  57. class ExpressionNode extends Node {  

  58.     private ArrayList<Node> list = new ArrayList<Node>(); //定义一个集合用于存储多条命令  

  59.       

  60.     public void interpret(Context context) {  

  61.         //循环处理Context中的标记  

  62.         while (true){  

  63.             //如果已经没有任何标记,则退出解释  

  64.             if (context.currentToken() == null) {  

  65.                 break;  

  66.             }  

  67.             //如果标记为END,则不解释END并结束本次解释过程,可以继续之后的解释  

  68.             else if (context.currentToken().equals("END")) {  

  69.                 context.skipToken("END");  

  70.                 break;  

  71.             }  

  72.             //如果为其他标记,则解释标记并将其加入命令集合  

  73.             else {  

  74.                 Node commandNode = new CommandNode();  

  75.                 commandNode.interpret(context);  

  76.                 list.add(commandNode);  

  77.             }  

  78.         }  

  79.     }  

  80.       

  81.     //循环执行命令集合中的每一条命令  

  82.     public void execute() {  

  83.         Iterator iterator = list.iterator();  

  84.         while (iterator.hasNext()){  

  85.             ((Node)iterator.next()).execute();  

  86.         }  

  87.     }  

  88. }  

  89.   

  90. //语句命令节点类:非终结符表达式  

  91. class CommandNode extends Node {  

  92.     private Node node;  

  93.       

  94.     public void interpret(Context context) {  

  95.         //处理LOOP循环命令  

  96.         if (context.currentToken().equals("LOOP")) {  

  97.             node = new LoopCommandNode();  

  98.             node.interpret(context);  

  99.         }  

  100.         //处理其他基本命令  

  101.         else {  

  102.             node = new PrimitiveCommandNode();  

  103.             node.interpret(context);  

  104.         }  

  105.     }  

  106.       

  107.     public void execute() {  

  108.         node.execute();  

  109.     }  

  110. }  

  111.   

  112. //循环命令节点类:非终结符表达式  

  113. class LoopCommandNode extends Node {  

  114.     private int number; //循环次数  

  115.     private Node commandNode; //循环语句中的表达式  

  116.       

  117.     //解释循环命令  

  118.     public void interpret(Context context) {  

  119.         context.skipToken("LOOP");  

  120.         number = context.currentNumber();  

  121.         context.nextToken();  

  122.         commandNode = new ExpressionNode(); //循环语句中的表达式  

  123.         commandNode.interpret(context);  

  124.     }  

  125.       

  126.     public void execute() {  

  127.         for (int i=0;i<number;i++)  

  128.             commandNode.execute();  

  129.     }  

  130. }  

  131.   

  132. //基本命令节点类:终结符表达式  

  133. class PrimitiveCommandNode extends Node {  

  134.     private String name;  

  135.     private String text;  

  136.       

  137.     //解释基本命令  

  138.     public void interpret(Context context) {  

  139.         name = context.currentToken();  

  140.         context.skipToken(name);  

  141.         if (!name.equals("PRINT") && !name.equals("BREAK") && !name.equals ("SPACE")){  

  142.             System.err.println("非法命令!");  

  143.         }  

  144.         if (name.equals("PRINT")){  

  145.             text = context.currentToken();  

  146.             context.nextToken();  

  147.         }  

  148.     }  

  149.       

  150.     public void execute(){  

  151.         if (name.equals("PRINT"))  

  152.             System.out.print(text);  

  153.         else if (name.equals("SPACE"))  

  154.             System.out.print(" ");  

  155.         else if (name.equals("BREAK"))  

  156.             System.out.println();  

  157.     }  

  158. }  

       在本实例代码中,环境类Context类似一个工具类,它提供了用于处理指令的方法,如nextToken()currentToken()skipToken()等,同时它存储了需要解释的指令并记录了每一次解释的当前标记(Token),而具体的解释过程交给表达式解释器类来处理。我们还可以将各种解释器类包含的公共方法移至环境类中,更好地实现这些方法的重用和扩展。

       针对本实例代码,我们编写如下客户端测试代码:

[java] view plaincopy

  1. class Client{  

  2.     public static void main(String[] args){  

  3.         String text = "LOOP 2 PRINT 杨过 SPACE SPACE PRINT 小龙女 BREAK END PRINT 郭靖 SPACE SPACE PRINT 黄蓉";  

  4.         Context context = new Context(text);  

  5.               

  6.         Node node = new ExpressionNode();  

  7.         node.interpret(context);  

  8.         node.execute();  

  9.     }  

  10. }  

       编译并运行程序,输出结果如下:

杨过     小龙女

杨过     小龙女

郭靖     黄蓉

 

 思考预测指令“LOOP    2 LOOP 2 PRINT杨过 SPACE SPACE    PRINT 小龙女 BREAK END PRINT   郭靖 SPACE SPACE PRINT 黄蓉    BREAK END”的输出结果。