最近在學習設計模式,比較巧合的是,昨天在看其他人博客的是,發現了一道比較有意思的面試題目,想用比較好的方法來設計他,一路思考,今天抽出午休時間完成了他,基於之前學習的設計模式系列之裝飾模式(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移動兩步,我們使用裝飾模式都可以非常輕松的實現,並且代碼邏輯清晰.
設計模式的核心理念:
- 對接口編程而不是對實現編程
- 少用繼承多用組合
- 代碼要對修改關閉,對拓展開放