四、以画画讲解Interpreter模式

一、案例基本介绍

本案例通过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;
    }

    @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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值