设计模式之装饰者模式应用案例(一)

最近在学习设计模式,比较巧合的是,昨天在看其他人博客的是,发现了一道比较有意思的面试题目,想用比较好的方法来设计他,一路思考,今天抽出午休时间完成了他,基于之前学习的设计模式系列之装饰模式(DECORATOR PATTERN),我发现这个题目非常适合使用装饰器模式,顺便标注以前原博主的文章链接:最近的一次面试.

代码下载地址:设计模式的学习 src/main/com/zhoutao123/design目录
我们首先看题目

题目要求

一队机器人漫游车将被美国宇航局降落在火星高原上。漫游车将在这个奇怪的长方形高原上巡游,以便他们的机载摄像头可以获得周围地形的完整视图,并将其发送回地球。漫游者的坐标和位置由x和y坐标的组合以及代表四个方向(E, S, W, N)的字母表示。高原划分为网格以简化导航。比如位置0,0,N,表示漫游车位于左下角并面向北。为了控制漫游车,美国宇航局发送一串简单的字母。指令字母是’L’,’R’和’M’。 ‘L’和’R’使漫游车分别向左或向右旋转90度,而不会从当前地点移动。 ‘M’表示前进一个网格点,并保持相同的方向。
假设从(x,y)直接向北移动,就到了(x,y + 1)。

INPUT:

  • 第一行输入是平台的右上角坐标,左下角坐标被假定为0,0。
    其余的输入是有关已部署的漫游车的信息。每个漫游车都有两行输入。第一行给出了漫游车的位置,第二行是告诉漫游车如何探索高原的一系列指令。位置由两个整数和一个由空格分隔的字母组成,对应于x和y坐标以及漫游车当前的方向。
    每个漫游车将按顺序完成,这意味着第二个漫游车在第一个漫游车完成移动之前不会开始移动。

OUTPUT:

  • 每个漫游车的输出应该是其最终的坐标和位置。

效果示例

  • 输入:
    • 5 5
    • 1 2 N
    • LMLMLMLMM
    • 3 3 E
    • MMRMMRMRRM
  • 预期产出:
    • 1 3 N
    • 5 1 E

由于这里只考虑怎么实现,所以就不写那些简单的输入提示了,我直接使用变量代替.

问题分析

从装饰模式考虑,我们有一个原点位置,然后经过移动,左右转向到达一个新的位置,因此我们可以定义移动,左转,右转操作,来装饰原点,当装饰然后之后我们只要调用修饰完成的点的方法之后,他就会一层一层的向深处执行,然后返回,类似于入栈和出栈(之前也考虑到使用栈来实现,有时间可以尝试使用栈来实现).入栈的时候定义操作,出站的时候执行操作,一步一步按照我们设定的方法执行.

具体代码

操作抽象类


package com.zhoutao123.design.example;

public abstract class Operate {
    // 声明为public,主要是懒得写get/set方法
    public int x;
    public int y;
   // 方向使用枚举类
    public Direction direction;


    // 抽象方法,执行操作,不同的操作不同的实现
   abstract Operate exec();

    // 主要是方便打印数据
    @Override
    public String toString() {
        return "坐标{" +
                "x=" + x +
                ", y=" + y +
                ", 方向=" + direction +
                '}';
    }
    // 方向枚举类
    enum Direction {
        N, S, W, E
    }
}

位置类

位置location只要在个个操作之间传递,用于定义初始化的点,因此继承了Operate

package com.zhoutao123.design.example;

public class Localtion extends Operate {

    public Localtion(int x,int y,Direction direction) {
        this.x = x;
        this.y = y;
        this.direction = direction;
    }
    @Override
    Operate exec() {
        return this;
    }
}

操作实现类

这里主要是实现不同的对点的操作,声明如下:

  • public class TurnLeft extends Operate {} 向左转

  • public class TurnRight extends Operate {} 向右转

  • public class TurnMove extends Operate {} 移动一个单位

下面看主要代码

向左转向

package com.zhoutao123.design.example;

public class TurnLeft extends Operate {
    // 持有下一个动作,构造的时候传入
    private Operate operate;

    public TurnLeft(Operate operate) {
        this.operate = operate;
    }

    @Override
    public Operate exec() {
        // 调用下一个动作exec方法,获得位置新
        this.operate = operate.exec();
        // 根据自己的实现的功能实现转向
        switch (operate.direction) {
            case E:
                operate.direction = Direction.N;
                break;
            case S:
                operate.direction = Direction.E;
                break;
            case W:
                operate.direction = Direction.S;
                break;
            case N:
                operate.direction = Direction.W;
        }
        // 向上一层返回操作后的点的位置信息
        return this.operate;
    }

}

向右转向

和向左转非常类似,不再赘述

package com.zhoutao123.design.example;

public class TurnRight extends Operate {

    private Operate operate;

    public TurnRight(Operate operate) {
        this.operate = operate;
    }

    @Override
    public Operate exec() {
        this.operate = operate.exec();
        switch (operate.direction) {
            case E:
                operate.direction = Direction.S;
                break;
            case S:
                operate.direction = Direction.W;
                break;
            case W:
                operate.direction = Direction.N;
                break;
            case N:
                operate.direction = Direction.E;
        }
        return this.operate;
    }

}

移动命令

package com.zhoutao123.design.example;


public class TurnMove extends Operate {

    private Operate operate;


    public TurnMove(Operate operate) {
        this.operate = operate;
    }


    @Override
    public Operate exec() {
        this.operate = operate.exec();
        switch (operate.direction) {
            case E:
                operate.x++;
                break;
            case S:
                operate.y--;
                break;
            case W:
                operate.x--;
                break;
            case N:
                operate.y++;
        }
        if (operate.x < 0 || operate.y < 0 || operate.x > 5 || operate.y > 5) {
            throw new IllegalStateException("命令有误,小车已经掉下悬崖");
        }
        return this.operate;
    }
}

测试代码

完成了三个操作,以及初始化点之后,我们尝试测试下.

需要说明的是,这里我并没有按照题目要求实现输入,直接定义了一个String来测试的,也就是说我只是实现了核心功能.

package com.zhoutao123.design.example;

import java.util.Scanner;

public class Test {

    public static void main(String[] args) {

        // 定义命令
        String command1 = "LMLMLMLMM";
        // 定义初始化的点
        Operate initOpera1 = new Localtion(1, 2, Operate.Direction.N);
        // 输出结果
        System.out.println(String.format("经过命令%s后移动到%s",command1,packageCommand(command1,initOpera1).exec()));

        String command2 = "MMRMMRMRRM";
        Operate initOpera2 = new Localtion(3, 3, Operate.Direction.E);
        System.out.println(String.format("经过命令%s后移动到%s",command2,packageCommand(command2,initOpera2).exec()));

        String command3 = "MLMRMLMRMLMRMLMRMLM";
        Operate initOpera3 = new Localtion(0, 0, Operate.Direction.E);
        System.out.println(String.format("经过命令%s后移动到%s",command3,packageCommand(command3,initOpera3).exec()));
    }

    // 开始装饰疯转操作
    public static  Operate packageCommand(String commandStr,Operate operate){
        for (char command : commandStr.toCharArray()) {
            switch (command) {
                case 'L':
                    operate = new TurnLeft(operate);
                    break;
                case 'R':
                    operate = new TurnRight(operate);
                    break;
                case 'M':
                    operate = new TurnMove(operate);
                    break;
                default:
                    throw new IllegalArgumentException("非法的命令,程序退出");
            }
        }
        return operate;
    }
}

测试结果

经过命令LMLMLMLMM后移动到坐标{x=1, y=3, 方向=N}
经过命令MMRMMRMRRM后移动到坐标{x=5, y=1, 方向=E}
经过命令MLMRMLMRMLMRMLMRMLM后移动到坐标{x=5, y=5, 方向=N}

总结

可以看到装饰模式的使用让代码看起来非常的整洁,逻辑清晰,而且易于拓展,假设有一天新的需求过来,要求实现45°转弯,这时候我们之要是实现向左转45°和向右转45°(实现方法和TurnLeft以及Left类似),然后Move操作在向左或者向右转45°的之后,合理的处理xy即可.甚至说,Move移动两步,我们使用装饰模式都可以非常轻松的实现,并且代码逻辑清晰.

设计模式的核心理念:

  • 对接口编程而不是对实现编程
  • 少用继承多用组合
  • 代码要对修改关闭,对拓展开放
上一篇:Educational Codeforces Round 60 (Rated for Div. 2) C. Magic Ship


下一篇:java并发编程 | 锁详解:AQS,Lock,ReentrantLock,ReentrantReadWriteLock