0
点赞
收藏
分享

微信扫一扫

四、以画画讲解Interpreter模式

一、案例基本介绍

本案例通过​​program repeat 4 repeat 2 go right end go left end end​​​这段代码,以实现如下效果:
四、以画画讲解Interpreter模式_java

案例基本分析

将上述案例想象成一辆小车,该小车按照上面的轨迹进行运动。而对于小车的运动,我们这里规定只有前行(​​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;
}

@Override
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() {
@Override
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 模式");
}
}


举报

相关推荐

0 条评论