一、案例基本介绍
本案例通过program repeat 4 repeat 2 go right end go left end end这段代码,以实现如下效果:
案例基本分析
将上述案例想象成一辆小车,该小车按照上面的轨迹进行运动。而对于小车的运动,我们这里规定只有前行(go)、右转(right)、左转(left),然后再加上重复(repeat)上述动作,所以program repeat 4 repeat 2 go right end go left end end这段代码可以分解为如下:
program 程序开始repeat 循环开始(外侧)
4 循环的次数
repeat 循环开始(内侧)
2 循环的次数
go 前进
right 右转
end 循环结束(内侧)
go 前进
left 左转
end 循环结束(外侧)
end 程序结束
二、程序设计思路
(一)算法设计
对上面的代码段进行解析,可以总结如下:
#EBNF 范式<program> ::= program <command list>
<command list> ::= <command>* end
<command> ::= <repeat command> | <primitive command>
<repeat command> ::= repeat <number> <command list>
<primitive command> ::= go | right | left
按照面向对象的思维,可以将以上对象抽象成如下接口:
Node 顶级抽象接口,共有方法:解析ProgramNode 对应 <program>
CommandListNode 对应 <command list>
CommandNode 对应 <command>
PrimitiveNode 对应 <primitive command>
RepeatNode 对应 <repeat command>
Context 对应 “解析对象”
同时,运用 Facade 设计模式,对内调用各个逻辑类,对外提供一个简单统一的接口,方便客户端调用。
名字
说明
Node
表示语法树“节点”类
ProgramNode
对应 <program> 的类
CommandListNode
对应 <command list> 的类
CommandNode
对应 <command> 的类
PrimitiveNode
对应 <primitive command> 的类
RepeatNode
对应 <repeat command> 的类
Context
表示语法解析上下文的类
ParseException
表示语法解析中可能会发生的异常的类
InterpreterFacade
对外提供统一调用接口
(二)应用设计
具体到画画,那么则需要一个具体执行接口: Executor,同时执行接口具体实现又分为:控制前进的 GoExecutor 和 控制方向的 DirectionExecutor,这里可以利用工厂模式创建 Executor ,工厂接口为:ExecutorFactory。
名字
说明
Executor
执行接口
ExecutorFactory
执行接口工厂
ExecutorFactory
执行接口工厂
BaseExecutor
执行接口基类
GoExecutor
前进执行类
DirectionExecutor
前进执行类
ExecuteException
表示执行动作中可能会发生的异常的类
三、具体代码
Executor.java
public interface Executor {void execute() throws ExecuteException;
}
Node.java
public interface Node extends Executor {void parse(Context context) throws ParseException;
}
ProgramNode.java
public class ProgramNode implements Node {private Node commandListNode;
public void parse(Context context) throws ParseException {
context.skipToken("program");
commandListNode = new CommandListNode();
commandListNode.parse(context);
}
public void execute() throws ExecuteException {
commandListNode.execute();
}
}
CommandListNode.java
public class CommandListNode implements Node {private List<Node> commandNodes = new ArrayList<Node>();
public void parse(Context context) throws ParseException {
while (true) {
String currentToken = context.getCurrentToken();
if (currentToken == null) {
throw new ParseException("Missing end here");
} else if ("end".equals(currentToken)) {
context.skipToken("end");
break;
} else {
Node commandNode = new CommandNode();
commandNode.parse(context);
commandNodes.add(commandNode);
}
}
}
public void execute() throws ExecuteException {
for (Node commandNode : commandNodes) {
commandNode.execute();
}
}
}
CommandNode.java
public class CommandNode implements Node {private Node node;
public void parse(Context context) throws ParseException {
String currentToken = context.getCurrentToken();
if ("repeat".equals(currentToken)) {
node = new RepeatCommandNode();
} else {
node = new PrimitiveNode();
}
node.parse(context);
}
public void execute() throws ExecuteException {
node.execute();
}
}
RepeatCommandNode.java
public class RepeatCommandNode implements Node {private int number;
private Node commandListNode;
public void parse(Context context) throws ParseException {
context.skipToken("repeat");
number = context.getCurrentNumber();
commandListNode = new CommandListNode();
commandListNode.parse(context);
}
public void execute() throws ExecuteException {
for (int i = 0; i < number; i++) {
commandListNode.execute();
}
}
}
PrimitiveNode.java
public class PrimitiveNode implements Node {private Executor executor;
public void parse(Context context) throws ParseException {
String currentToken = context.getCurrentToken();
context.skipToken(currentToken);
if (!"go".equals(currentToken) && !"right".equals(currentToken) && !"left".equals(currentToken)) {
throw new ParseException(currentToken + "is not expected here");
}
executor = context.createExecutor(currentToken);
}
public void execute() throws ExecuteException {
executor.execute();
}
}
Context.java
public class Context implements ExecutorFactory {private StringTokenizer tokenizer;
private String currentToken;
private ExecutorFactory factory;
public Context(String text) {
this.tokenizer = new StringTokenizer(text);
nextToken();
}
public String getCurrentToken() {
return currentToken;
}
public void setFactory(ExecutorFactory factory) {
this.factory = factory;
}
public String nextToken() {
if (tokenizer.hasMoreTokens()) {
currentToken = tokenizer.nextToken();
} else {
currentToken = null;
}
return currentToken;
}
public void skipToken(String token) throws ParseException {
if (token != null && token.trim().length() > 0) {
if (token.equals(currentToken)) {
nextToken();
} else {
throw new ParseException("Warning: " + token + "is expected, but " + currentToken + "is found!");
}
}
}
public int getCurrentNumber() throws ParseException {
int number;
try {
number = Integer.parseInt(currentToken);
nextToken();
} catch (NumberFormatException e) {
throw new ParseException(currentToken + " format failure");
}
return number;
}
public Executor createExecutor(String name) {
return factory.createExecutor(name);
}
}
ParseException.java
public class ParseException extends Exception {public ParseException(String message) {
super(message);
}
}
InterpreterFacade.java
public class InterpreterFacade implements Executor {private ExecutorFactory factory;
private Node programNode;
public InterpreterFacade(ExecutorFactory factory) {
this.factory = factory;
}
public boolean parse(String text) {
boolean ok = true;
Context context = new Context(text);
context.setFactory(factory);
programNode = new ProgramNode();
try {
programNode.parse(context);
} catch (ParseException e) {
e.printStackTrace();
ok = false;
}
return ok;
}
public void execute() throws ExecuteException {
programNode.execute();
}
}
ExecutorFactory.java
public interface ExecutorFactory {Executor createExecutor(String name);
}
BaseExecutor.java
public abstract class BaseExecutor implements Executor {protected TurtleCanvas canvas;
public BaseExecutor(TurtleCanvas canvas) {
this.canvas = canvas;
}
public abstract void execute();
}
GoExecutor.java
public class GoExecutor extends BaseExecutor {private int length;
public GoExecutor(TurtleCanvas canvas, int length) {
super(canvas);
this.length = length;
}
public void execute() {
canvas.go(length);
}
}
DirectionExecutor.java
public class DirectionExecutor extends BaseExecutor {private int relativeDirection;
public DirectionExecutor(TurtleCanvas canvas, int relativeDirection) {
super(canvas);
this.relativeDirection = relativeDirection;
}
public void execute() {
canvas.setRelativeDirection(relativeDirection);
}
}
ExecuteException.java
public class ExecuteException extends Exception {public ExecuteException(String message) {
super(message);
}
}
TurtleCanvas.java
public class TurtleCanvas extends Canvas implements ExecutorFactory {final static int UNIT_LENGTH = 30;
final static int DIRECTION_UP = 0;
final static int DIRECTION_RIGHT = 3;
final static int DIRECTION_DOWN = 6;
final static int DIRECTION_LEFT = 9;
final static int RELATIVE_DIRECTION_RIGHT = 3;
final static int RELATIVE_DIRECTION_LEFT = -3;
final static int RADIUS = 3;
private int direction;
private Point position;
private Executor executor;
public TurtleCanvas(int width, int height) {
setSize(width, height);
initialize();
}
private void initialize() {
Dimension size = getSize();
position = new Point(size.width / 2, size.height / 2);
direction = 0;
setForeground(Color.red);
setBackground(Color.white);
Graphics graphics = getGraphics();
if (graphics != null) {
graphics.clearRect(0, 0 , size.width, size.height);
}
}
public void setExecutor(Executor executor) {
this.executor = executor;
}
public void go(int length) {
int newX = position.x;
int newY = position.y;
switch (direction) {
case DIRECTION_UP:
newY -= length;
break;
case DIRECTION_RIGHT:
newX += length;
break;
case DIRECTION_DOWN:
newY += length;
break;
case DIRECTION_LEFT:
newX -= length;
break;
default:
break;
}
Graphics g = getGraphics();
if (g != null) {
g.drawLine(position.x, position.y, newX, newY);
g.fillOval(newX - RADIUS, newY - RADIUS, RADIUS * 2 + 1, RADIUS * 2 + 1);
}
position.x = newX;
position.y = newY;
}
public void setRelativeDirection(int relativeDirection) {
setDirection(relativeDirection + direction);
}
public void setDirection(int direction) {
this.direction = (12 + direction) % 12;
// if (direction < 0) {
// direction = 12 - (-direction) % 12;
// } else {
// direction = direction % 12;
// }
// this.direction = direction % 12;
}
public void paint(Graphics g) {
initialize();
if (executor != null) {
try {
executor.execute();
} catch (ExecuteException e) {
e.printStackTrace();
}
}
}
public Executor createExecutor(String name) {
Executor executor = null;
if ("go".equals(name)) {
executor = new GoExecutor(this, UNIT_LENGTH);
} else if ("right".equals(name)) {
executor = new DirectionExecutor(this, RELATIVE_DIRECTION_RIGHT);
} else if ("left".equals(name)) {
executor = new DirectionExecutor(this, RELATIVE_DIRECTION_LEFT);
}
return executor;
}
}
DemoMain.java
public class DemoMain extends Frame implements ActionListener {private TurtleCanvas turtleCanvas = new TurtleCanvas(400, 400);
private InterpreterFacade facade = new InterpreterFacade(turtleCanvas);
private TextField programTextField = new TextField("program repeat 3 go right go left end end");
public void actionPerformed(ActionEvent e) {
if (e.getSource() == programTextField) {
parseAndExecute();
}
}
private void parseAndExecute() {
String programText = programTextField.getText();
System.out.println("programText = " + programText);
facade.parse(programText);
turtleCanvas.repaint();
}
public DemoMain(String title) {
super(title);
turtleCanvas.setExecutor(facade);
setLayout(new BorderLayout());
programTextField.addActionListener(this);
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
add(programTextField, BorderLayout.NORTH);
add(turtleCanvas, BorderLayout.CENTER);
pack();
parseAndExecute();
setVisible(true);
}
public static void main(String[] args) {
new DemoMain("Interpreter 模式");
}
}