与Command模式一样,Interpreter模式也会产生一个可执行的对象.差别在于Interpreter模式会创建一个类层次,其中每个类会实现或者解释一个公共操作,这个操作与类的名称相匹配.在这方面,Interpreter模式类似于State模式和Strategy模式.在Interpreter模式、State模式以及Strategy模式中,一个公共操作遍布类集合,每个类都以不同的方式实现这个操作。
Interpreter模式也类似于Composite模式。Composite模式通常会为单个对象和群组对象定义一个公共接口。不过,Composite模式并不要求支持以不同方式组织的结构,尽管该模式可以支持这些结构。例如,介绍Composite模式时曾描述过ProcessComponent类层次结构,该结构允许生产流程顺序进行,也允许生产流程交替进行。Interpreter模式通常都会涉及不同类型的组合结构(Interpreter模式通常处于Composite模式结构之上)。一个类组成其他组件的方式定义了解释器类实现或解释一个操作的方式。
Interpreter模式的主要意图是可以按照自己定义的组合规则集合来组合可执行对象。
1.Interpreter模式范例:
Oozinoz公司的机器人可以沿着生产线移动原材料。Oozinoz公司开发人员提供一个解释器,用于控制这些机器人;另外,该解释器对生产线上的机器具有一定的控制能力。你也许认为解释器是编程语言,实际上解释器允许对指令进行组合。Oozinoz机器人解释器实际上就是一个类层次结构,它封装了控制机器人的命令。该类层次结构的顶部是一个抽象的Command类。execute()方法遍布该类层次结构中。图1显示了Robot类以及机器人解释器支持的两个命令。
解释器类层次结构提供一种在运行时对工厂机器人进行编程的能力
第一眼看该图时,发现类层次的最高层是Command类,你也许会建议使用Command模式。但是Command模式的目的是在对象中封装方法。图1的Command类层次结构不是这样做的,这个类层次结构要求Command子类重新解释execute()操作的含义。这就是Interpreter模式的目的:允许你组合复杂的对象。
典型的解释器类层次包含至少两个子类,我们将很快扩展Command类层次.图1的两个类足够实现本书的范例要求,代码如下所示:
package app.interpreter;
import com.oozinoz.machine.*:
import com.oozinoz.robotInterpreter.*;
public class ShowInterpreter {
public static void main(String[] args){
MachineComposite dublin = OozinozFactory.dublin();
ShellAssembler assembler = (ShellAssembler)dublin.find("ShellAssembler:3302");
StarPress press = (StarPress)dublin.find("StarPress:3404");
Fuser user = (Fuser)dublin.find("Fuser:");
assembler.load(new Bin(11011));
press.load(new Bin(11015));
CarryCommand carry1 = new CarryCommand(assembler,fuser);
CarryCommand carry2 = new CarryCommand(press,fuser);
CommandSequence seq = new CommandSequence();
seq.add(carry1); seq.add(carry2);
seq.execute();
}
}
上述演示代码让工厂机器人把两箱原材料从操作机器转移到一个倾销的缓冲池中,这需要使用OozinozFactory类的dublin()方法返回的机器组合对象.这个数据模型表示位于爱尔兰都柏林的新Oozinoz工厂.上述代码定位这个工厂中的三台机器,把原材料箱放在其中两台机器上面,组合Command类层次结构提供的命令.程序的最后一条语句CommandSequence对象的execute()方法,导致机器人按照seq命令中的指令开始操作.
CommandSequence对象解释execute()操作,把整个方法调用转发给每个子命令:
package com.oozinoz.robotInterpreter;
import java.util.ArrayList;
import java.util.List;
public class CommandSequence extends Command {
protected List commands = new ArrayList();
public void add(Command c){
commands.add(c);
}
public void execute(){
for(int i=0;i<commands.size();i++){
Command c = (Command)commands.get(i); c.execute();
}
}
}
CarryCommand类完成execute()操作的过程如下 :与工厂机器人进行交互,把材料箱从一个机器移到其他机器.
package com.oozinoz.robotInterpreter;
import com.oozinoz.machine.Machine;
public class CarryCommand extends Command {
protected Machine fromMachine;
protected Machine toMachine;
public CarryCommand( Machine fromMachine,Machine toMachine){
this.fromMachine = fromMachine;
this.toMachine = toMachine;
}
public void execute(){
Robot.singleton.carry(fromMachine,toMachine);
}
}
CarryCOmmand类专用于机器人控制工厂生产线的领域环境下.我们很容易想像其他领域相关的类,诸如控制机器的StartUpCommand和ShutdownCommand类.创建操作多个特定机器的Forcommand类也是有用的.图2给出对Command类层次的扩展.
Interpreter模式允许多个子类重新解释公共操作的含义
ForCommand类的部分设计思路是非常显而易见的.推测起来,该类的构造器接收机器集合和命令对象,以便作为for循环体处理.这种设计更难的部分是如何把方法体和循环连接起来.Java5对for语句进行了扩展,建立了一个新变量,每次循环体执行一次,该变量就接收一个新值.我们将模拟这种处理方式.请参考如下语句:
for(Command c:commands) c.execute();
Java把for语句声明中的c标识符与循环体中的c变量链接起来.为创建模拟这种处理方式的解释器类,我们需要处理和计算变量的方式.图3的Term类层次结构说明了处理方式.
提供变量对机器进行处理的Term类层次结构
Term类层次结构类似于Command类层次结构的地方在于类层次中到处存在的公共操作(eval()).你也许认为这个类层次本身是Interpreter模式的范例,尽管它并没有提供通常与Interpreter模式一起使用的组合类,诸如CommandSequence.
Term类层次结构允许我们把每个机器命名为常量,可以给这些常量或者其他变量分配变量.也允许我们让域相关的解释器类更加灵活.比如,StartUpCommand代码可以与Term对象一起使用,而不是只能应用于特定机器.
package com.oozinoz.robotInterpreter2;
import com.oozinoz.machine.Machine;
public class StartUpCommand extends Command {
protected Term term;
public StartUpCommand(Term term){
this.term = term;
}
public void execute(){
Machine m = term.eval();
m.startup();
}
}
同样,为了增强CarrryCommand类的灵活性,我们可以修改该类,以便于使用Term对象,修改代码如下:
package com.oozinoz.robotInterpreter2;
public class CarryCommand extends Command {
protected Term from;
protected Term to;
public CarryCommand(Term fromTerm,Term toTerm){
from = fromTerm;
to = toTerm;
}
public void execute(){
Robot.singleton.carry(from.eval(),to.eval());
}
}
一旦Command类层次可以操作Term对象,就可以修改ForCommand类,使之设置变量的值,并在循环中执行方法体命令.
package com.oozinoz.robotInterpreter2;
import java.util.List;
import com.oozinoz.machine.Machine;
import com.oozinoz.machine.MachineComponent;
import com.oozinoz.machine.MachineComposite;
public class ForCommand extends Command {
protected MachineComponent root;
protected Variable variable;
protected Command body;
public ForCommand(MachineComponent mc,Variable v,Command body){
this.root = mc;
this.variable = variable;
this.body = body;
}
public void execute(){
execute(root);
}
public void execute(MachineComponent mc){
if(mc instanceof Machine){
Machine m = (Machine)mc;
variable.assign(new Constant(m));
body.execute(); return;
}
MachineComposite comp = (MachineComposite)mc;
List children = comp.getComponents();
for(int i = 0;i<children.size();i++){
MachineComponent child = (MachineComponent)children.get(i);
execute(child);
}
}
}
ForCommand类中的execute()代码使用投射来查看机器组件树.28章将介绍一种更好的技术来对组合结构进行迭代处理.对于Interpreter模式,最重要的一点是为树中每个节点正确解释execute()请求.
借助于ForCommand类,我们可以开始为工厂组合命令的"程序"或者"脚本".比如,下面程序组合一个解释器对象,可以关闭一个工厂中的所有机器.
package app.interpreter;
import com.oozinoz.machine.*;
import com.oozinoz.robotInterpreter2.*;
class ShowDown {
public static void main(String[] args){
MachineComposite dublin = OozinozFactory.dublin();
Variable v = new Variable("machine");
Command c = new ForCommand(dublin,v,new ShutDownCommand(v));
c.execute();
}
}
当这个程序调用execute()方法时,ForCommand对象c会解释execute()方法,方式为:遍历所提供的机器组件,并且为每个机器完成如下操作:
(1)设置变量v的值;
(2)调用已提供ShutDownCommand对象的execute()操作.
如果我们添加控制处理逻辑请求的类,诸如IfCommand类和WhileCommand类,可以得到一个更加完善的解释器.这些类需要某种方式来模拟Boolean条件.比如,我们也许需要某种方式来模拟机器变量是否等于特定机器的情况.我们也许会引入新的Term类层次结构,但是如果借用C语言的处理思想会更加简单:让null代表假,其余的值代表真.借助于这个思路,我们可以扩展Term类层次结构.如下图所示:
Term类层次结构包含模拟Boolean条件的类
Equals类比较两个条件,并返回null来代表假.更合理的设计思路是如果两个条件相等,Equals类的eval()方法返回其中一个条件,如下所示:
package com.oozinoz.robotInterpreter2;
import com.oozinoz.machine.Machine;
public class Equals extends Term {
protected Term term1;
protected Term term2;
public Equals(Term term1,Term term2){
this.term1 = term1;
this.term2 = term2;
}
public Machine eval(){
Machine m1 = term1.eval();
Machine m2 = term2.eval();
return m1.equals(m2)?m1:null;
}
}
HasMaterial类把Boolean类中值的概念扩展到特定领域的例子,诸如如下代码:
package com.oozinoz.robotInterpreter2;
import com.oozinoz.machine.Machine;
public class HasMaterial extends Term {
protected Term term;
public HasMaterial(Term term){
this.term = term;
}
public Machine eval(){
Machine m = term.eval();
return m.hasMaterial()?m:null;
}
}
现在我们已经把Boolean条件的概念添加到解释器包,接下来可以添加流程控制类,如图5所示.
通过往解释器类层次结构添加流程控制逻辑,解释器功能更加丰富
当我们需要一个不做任何事情的命令时,NullCommand类很有用处,诸如当if命令的else分支是空时:
package com.oozinoz.robotInterpreter2;
public class NullCommand extends Command {
public void execute(){ }
}
package com.oozinoz.robotInterpreter2;
public class IfCommand extends Command {
protected Term term;
protected Command body;
protected Command elseBody;
public IfCommand( Term term,Command body,Command elseBody){
this.term = term;
this.body = body;
this.elseBody = elseBody; }
public void execute(){
if(term.eval() != null) body.execute();
else elseBody.execute();
}
}
突破题:请完成IfCommand类中execute()方法的代码(上面已经写出).
突破题:请完成WhileCommand类的代码.
package com.oozinoz.robotInterpreter2;
public class WhileCommand extends Command {
protected Term term; protected Command body;
public WhileCommand(Term term,Command body){
this.term = term;
this.body = body;
}
public void execute(){
while(term.eval()!= null) body.execute();
}
}
你也许把WhileCommand类与能够卸载火药球填压机的解释器一起使用,代码如下:
package app.interpreter;
import com.oozinoz.machine.*;
import com.oozinoz.robotInterpter2.*;
public class ShowWhile {
public static void main(String[] args){
MachineComposite dublin = OozinozFactory.dubline();
Term starPress = new Constant( (Machine) dublin.find("StarPress:1401"));
Term fuser = new Constant( (Machine) dublin.find("Fuser:1101"));
starPress.eval().load(new Bin(77));
starPress.eval().load(new Bin(88));
whileCommand command = new WhileCommand(
new HasMaterial(starPress),new CarryCommand(starPress,fuser));
command.execute();
}
}
Command对象是个解释器,可以解释execute()方法,从火药球填压机1401卸载所有火药箱.
突破题:解释Command模式和Interpreter模式之间的区别.答:Interpreter模式的意图在于使开发人员可以组合可执行对象,这些对象来自于对某公共操作提供各种解释的类层次结构.命令模式的意图仅仅是在一个对象中请求.
解释器对象能否作为命令使用?当然可以.具体应该使用哪种设计模式,这取决于你的意图.比如,你是期望创建组合可执行对象的工具集?还是期望将请求封装在对象中?总之,不同的需求要求使用不同的设计模式.
如想增加其他应用领域相关任务的控制,我们可以往解释器类层次结构添加更多功能.也可以扩展Term类层次结构.比如让Term子类发现靠近其他机器的卸载缓冲池.
Command和Term类层次结构的用户可以随意组合丰富而复杂的执行"程序".比如,创建下面一个对象并不难,当这个对象执行时,将卸载除工厂中卸载缓冲池之外的来自于所有机器的原材料.我们可以使用伪代码来描述这个程序,如下所示:
for(m in factory)
if (not (m is unloadBuffer))
ub = findUnload for m
while( m hasMaterial) carry(m,ub)
如果要编写Java代码来完成这些任务,相对于伪代码而言,Java代码会更加庞大和复杂.所以为什么不使用伪代码,通过创建获取领域语言的解析器来管理工厂中的原材料,并创建对应的解释器对象呢?
2.解释器、语言和解析器:
Interpreter模式意在强调解释器的工作方式,但是它并没有指定如何在运行时组合或者实例化它们。在本章,我们通过编写Java代码来“手工”生成新的解释器。但是创建一个新的解释器最常见的方法就是使用解析器。解析器对象可以按照预定规则识别文本和分解文本结构,以便于进一步处理。比如,你可以编写一个解析器,用它创建一个对应到早期伪代码形式文本程序的机器命令解释器对象。 用Java语言编写的解析器工具很少,讨论这个主题的书籍也非常少。可以使用关键字“Java 解析器工具"来搜索网络资源。大多数解析器都包含一个解析器生成器。为了使用这个生成器,可以使用专门的语法来描述这个模式,工具会根据描述产生解析器。所产生的解析器会识别出你的语言实例。有时,不必使用解析器生成工具,可以通过应用Interpreter模式来编写通用目的的解析器。Builder Parser with Java[Metsker,2001]使用Java语言解释了这种技术。
3. 小结:
Interpreter模式使我们可以根据创建的类层次结构来组合可执行对象。该类层次结构中的每个类分别实现一个公共操作,诸如execute()。尽管前面没有讨论过,但这个方法需要一个“上下文”对象,用来保存重要的状态信息。
每个类的名称通常意味着这个类实现这个公共操作的方式。每个类或者定义组合命令的方式,或者导致某种动作的终端命令。
解释器通常伴随着引入变量的设计,以及Boolean或算术表达式。解释器也经常使用解析器来简化新的解释器对象的创建过程。