zoukankan      html  css  js  c++  java
  • 设计模式之装饰者模式应用案例(一)

    最近在学习设计模式,比较巧合的是,昨天在看其他人博客的是,发现了一道比较有意思的面试题目,想用比较好的方法来设计他,一路思考,今天抽出午休时间完成了他,基于之前学习的设计模式系列之装饰模式(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移动两步,我们使用装饰模式都可以非常轻松的实现,并且代码逻辑清晰.

    设计模式的核心理念:

    • 对接口编程而不是对实现编程
    • 少用继承多用组合
    • 代码要对修改关闭,对拓展开放
  • 相关阅读:
    小程序隐藏或自定义 scroll-view滚动条
    小程序携带参数(单个或多个)跳转页面(实例)
    小程序接收from表单数据(实例)
    js返回上一页
    项目部署到线上后台进不去
    微信小程序取消button边框线
    阿里iconfont图库官网网址
    php 发送邮件(实例)
    PHP 数组序列化,转为字符串
    面向对象的设计原则
  • 原文地址:https://www.cnblogs.com/zhoutao825638/p/10382269.html
Copyright © 2011-2022 走看看