設計模式之裝飾者模式應用案例(一)


最近在學習設計模式,比較巧合的是,昨天在看其他人博客的是,發現了一道比較有意思的面試題目,想用比較好的方法來設計他,一路思考,今天抽出午休時間完成了他,基於之前學習的設計模式系列之裝飾模式(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移動兩步,我們使用裝飾模式都可以非常輕松的實現,並且代碼邏輯清晰.

設計模式的核心理念:

  • 對接口編程而不是對實現編程
  • 少用繼承多用組合
  • 代碼要對修改關閉,對拓展開放


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM