設計模式之命令模式(二)

来源:https://www.cnblogs.com/dimple91/archive/2019/05/15/10869038.html
-Advertisement-
Play Games

上一次留給大家去做的實踐,不知道大家執行的怎麼樣了呢。 我們通過一個簡單的練習,完成了一個控制開關。那現在,我們打算將遙控器的每個插槽,對應到一個命令這樣就要遙控器變成“調用者”。當按下按鈕,相應命令對象的execute()方法就會被調用,其結果就是,接收者(例如電燈、風扇、音響)的動作被調用。 實 ...


上一次留給大家去做的實踐,不知道大家執行的怎麼樣了呢。

我們通過一個簡單的練習,完成了一個控制開關。那現在,我們打算將遙控器的每個插槽,對應到一個命令這樣就要遙控器變成“調用者”。當按下按鈕,相應命令對象的execute()方法就會被調用,其結果就是,接收者(例如電燈、風扇、音響)的動作被調用。

實現遙控器

public class RemoteControl {
    Command[] onCommands;
    Command[] offCommands;
 

    public RemoteControl() {
        onCommands = new Command[7];
        offCommands = new Command[7];
// 在構造器中,只需實例化並初始化這兩個開與關的數組
        Command noCommand = new NoCommand();
        for (int i = 0; i < 7; i++) {
            onCommands[i] = noCommand;
            offCommands[i] = noCommand;
        }
    }

// 這個方法有三個參數,分別是插槽的位置、開的命令、關的命令。這些命令將記錄開關數組中對應的插槽位置,以供稍後使用
    public void setCommand(int slot, Command onCommand, Command offCommand) {
        onCommands[slot] = onCommand;
        offCommands[slot] = offCommand;
    }
 
// 當按下開或關的按鈕,硬體就會負責調用對應的方法,也就是onButtonWasPushed或offButtonWasPushed
    public void onButtonWasPushed(int slot) {
        onCommands[slot].execute();
    }
 
    public void offButtonWasPushed(int slot) {
        offCommands[slot].execute();
    }
  
    public String toString() {
        StringBuffer stringBuff = new StringBuffer();
        stringBuff.append("\n------ Remote Control -------\n");
        for (int i = 0; i < onCommands.length; i++) {
            stringBuff.append("[slot " + i + "] " + onCommands[i].getClass().getName()
                + "    " + offCommands[i].getClass().getName() + "\n");
        }
        return stringBuff.toString();
    }
}

實現命令

此前我們已經動手實現過LightOnCommand,純粹就是簡單的開和關命令。那現在,我們來為音響編寫開與關的命令。

音響的關閉是毫無難度,就是開啟的時候有點複雜,你知道為什麼嗎?難道音響開了就好了?是否還需要後續其他的動作才能讓音響響起來了?哎呀,小編多嘴了好像。

public class StereoOnWithCDCommand implements Command {
    Stereo stereo;
 
    public StereoOnWithCDCommand(Stereo stereo) {
        this.stereo = stereo;
    }
 
// 打開音響,需要三個步驟,開啟音響,設置CD播放,設置音量,不然就成啞巴了
    public void execute() {
        stereo.on();
        stereo.setCD();
        stereo.setVolume(11);
    }
}

這裡列舉了一個電燈,一個音響,差不多就把其他類似的都已經搞定了,比如電扇、門,對吧。所以,趕緊看看你之前動手的操作,是不是和小編的差不多。

讓我們繼續看下,多個的是怎麼實現的呢。

public class RemoteLoader {
 
    public static void main(String[] args) {
        RemoteControl remoteControl = new RemoteControl();
// 將所有的裝置創建在合適的位置
        Light livingRoomLight = new Light("Living Room");
        Light kitchenLight = new Light("Kitchen");
        CeilingFan ceilingFan= new CeilingFan("Living Room");
        GarageDoor garageDoor = new GarageDoor("");
        Stereo stereo = new Stereo("Living Room");
  
// 創建所有的電燈命令對象
        LightOnCommand livingRoomLightOn = 
                new LightOnCommand(livingRoomLight);
        LightOffCommand livingRoomLightOff = 
                new LightOffCommand(livingRoomLight);
        LightOnCommand kitchenLightOn = 
                new LightOnCommand(kitchenLight);
        LightOffCommand kitchenLightOff = 
                new LightOffCommand(kitchenLight);
  
// 創建弔扇的開與關命令
        CeilingFanOnCommand ceilingFanOn = 
                new CeilingFanOnCommand(ceilingFan);
        CeilingFanOffCommand ceilingFanOff = 
                new CeilingFanOffCommand(ceilingFan);
 
// 創建車庫門的上與下命令
        GarageDoorUpCommand garageDoorUp =
                new GarageDoorUpCommand(garageDoor);
        GarageDoorDownCommand garageDoorDown =
                new GarageDoorDownCommand(garageDoor);
                
// 創建音響的開與關命令
        StereoOnWithCDCommand stereoOnWithCD =
                new StereoOnWithCDCommand(stereo);
        StereoOffCommand  stereoOff =
                new StereoOffCommand(stereo);
 
// 現在已經有了全部的命令,我們將它們載入到遙控器插槽中
        remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
        remoteControl.setCommand(1, kitchenLightOn, kitchenLightOff);
        remoteControl.setCommand(2, ceilingFanOn, ceilingFanOff);
        remoteControl.setCommand(3, stereoOnWithCD, stereoOff);
  
        System.out.println(remoteControl);
 
// 在這裡逐步按下每個插槽的開與關按鈕
        remoteControl.onButtonWasPushed(0);
        remoteControl.offButtonWasPushed(0);
        remoteControl.onButtonWasPushed(1);
        remoteControl.offButtonWasPushed(1);
        remoteControl.onButtonWasPushed(2);
        remoteControl.offButtonWasPushed(2);
        remoteControl.onButtonWasPushed(3);
        remoteControl.offButtonWasPushed(3);
    }
}

寫文檔的時候到了

我們這個主要的設計目標就是讓遙控器代碼儘可能地簡單,這樣一來,新的廠商類一旦出現,遙控器並不需要隨之修改。因為,我們才用了命令模式,從邏輯上將遙控器的類和廠商的類解耦。我們相信這將降低遙控器的生產成本,並大大地減少維護時所需的費用。

下麵的類圖提供了設計的全貌:

撤銷哪去了?

別急別急,小編說的功能都會有的。撤銷功能使用起來就是這樣的:比如說客廳的電燈是關閉的,然後你按下遙控器上的開啟按鈕,自然電燈就被打開了。現在如果按下撤銷按鈕,那麼上一個動作將被倒轉,在這個例子里,電燈將被關閉。

同樣,我們先來一個簡單的撤銷示例。之前我們用的是execute()方法實現開啟或者關閉的調用,那麼我們用undo()方法來執行撤銷操作。即在Command介面里實現一個同execute()相反的方法undo(),然後在實現類里將undo()的動作做成和execute()相反的操作即可。

講的有點籠統?在這裡小編就不提供具體的代碼了,詳細的請看GitHub我的分享吧。

使用狀態實現撤銷

因為電燈這個開關已經撤銷,是很簡單的入門,小編沒有提供源碼在文中,但是因為還有電風扇這個存在,小編還不得不繼續搞一個高大上的方式。電扇不僅僅是開關,還有檔位的存在,對吧,是不是瞬間有思路了呢?

public class CeilingFan {
    public static final int HIGH = 3;
    public static final int MEDIUM = 2;
    public static final int LOW = 1;
    public static final int OFF = 0;
    String location;
    int speed;
 
    public CeilingFan(String location) {
        this.location = location;
        speed = OFF;
    }
  
    public void high() {
        speed = HIGH;
        System.out.println(location + " ceiling fan is on high");
    } 
 
    public void medium() {
        speed = MEDIUM;
        System.out.println(location + " ceiling fan is on medium");
    }
 
    public void low() {
        speed = LOW;
        System.out.println(location + " ceiling fan is on low");
    }
  
    public void off() {
        speed = OFF;
        System.out.println(location + " ceiling fan is off");
    }
  
    public int getSpeed() {
        return speed;
    }
}

現在我們就來實現風扇的撤銷。這麼做,需要追蹤弔扇的最後設置速度,如果undo方法被調用了,就要恢覆成之前弔扇速度的設置值。就如下麵這樣:

public class CeilingFanHighCommand implements Command {
    CeilingFan ceilingFan;
// 增加局部狀態以便追蹤弔扇之前的速度
    int prevSpeed;
  
    public CeilingFanHighCommand(CeilingFan ceilingFan) {
        this.ceilingFan = ceilingFan;
    }
 
    public void execute() {
// 我們改變弔扇的速度之前,需要先將它之前的狀態記錄起來,以便需要撤銷時使用
        prevSpeed = ceilingFan.getSpeed();
        ceilingFan.high();
    }
 
// 將弔扇的速度設置會之前的值,達到撤銷的目的
    public void undo() {
        if (prevSpeed == CeilingFan.HIGH) {
            ceilingFan.high();
        } else if (prevSpeed == CeilingFan.MEDIUM) {
            ceilingFan.medium();
        } else if (prevSpeed == CeilingFan.LOW) {
            ceilingFan.low();
        } else if (prevSpeed == CeilingFan.OFF) {
            ceilingFan.off();
        }
    }
}

讓我們來測試下風扇吧

條件都具備了,那我們來測試下吧。我們打算把0號插槽的開啟按鈕設置為中速,把第1號插槽的開啟按鈕設置成高速,代碼如下:

public class RemoteLoader {
 
    public static void main(String[] args) {
        RemoteControlWithUndo remoteControl = new RemoteControlWithUndo();

        CeilingFan ceilingFan = new CeilingFan("Living Room");
   
        CeilingFanMediumCommand ceilingFanMedium = 
                new CeilingFanMediumCommand(ceilingFan);
        CeilingFanHighCommand ceilingFanHigh = 
                new CeilingFanHighCommand(ceilingFan);
        CeilingFanOffCommand ceilingFanOff = 
                new CeilingFanOffCommand(ceilingFan);
  
        remoteControl.setCommand(0, ceilingFanMedium, ceilingFanOff);
        remoteControl.setCommand(1, ceilingFanHigh, ceilingFanOff);
   
        // 首先,我們以中速開啟弔扇
        remoteControl.onButtonWasPushed(0);
        // 然後關閉
        remoteControl.offButtonWasPushed(0);
        System.out.println(remoteControl);
        // 撤銷,應該會回到中速
        remoteControl.undoButtonWasPushed();
  
        // 這個時候開啟高速
        remoteControl.onButtonWasPushed(1);
        System.out.println(remoteControl);
        // 再進行一次撤銷,應該會回到中速
        remoteControl.undoButtonWasPushed();
    }
}

好了,至此我們不僅僅實現了單個的開與關,還實現了一整個遙控器所有控制項的開與關,甚至是複雜的家電的開與關(音響、電扇的開啟略複雜),而且均實現了撤銷。作為程式員的你是不是經常使用撤銷功能呢,反正我是經常使用的噢。

但是,這還不是終極狀態。我們在這裡只能實現一個家電的開與關,如果光憑按下一個按鈕,不能實現燈光、電視、音響的同步使用,那這個遙控器對我們來說是不是還是有點low呢?是吧,確實有點low,如何破解,敬請期待我們的下一篇。

愛生活,愛學習,愛感悟,愛挨踢


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前段時間遇到的一個需求,要求滑鼠懸停顯示使用描述, 用到了react-tooltip插件,今天寫一個總結 先看效果(為了方便參考,用的是原始樣式): 文檔參考地址: https://www.npmjs.com/package/react-tooltip 1, 首先在配置文件加上引用 2, 然後在頁面 ...
  • 摘要: 你所不知道的系列。 原文: "提高 JavaScript 開發效率的高級 VSCode 擴展之二!" 作者: "前端小智" "Fundebug" 經授權轉載,版權歸原作者所有。 作為一名業餘愛好者、專業人員,甚至是每月只有一次編程的開發人員,你必須知道,對於任何願意在工作中投入最大生產時間的 ...
  • 道路千萬條,安全第一條! 魔方結構解析 從魔方的外觀來看,可以有多種方式來表達它的組成,從而也有多種方式來形成一個魔方。如: 由六個面組成 由若幹層組成 由多個方塊組成 無論哪種方式,都可以製作魔方。只是,不同的方式對後續的其它操作會有影響,有些方式甚至會導致利用已有的特性無法直接表達。因此,在這項 ...
  • 1.代碼: ...
  • 工廠方法模式 定義 工廠方法(Factory Method)模式的意義是定義一個創建產品對象的工廠介面,將實際創建工作推遲到子類當中。核心工廠類不再負責產品的創建,這樣核心類成為一個抽象工廠角色,僅負責具體工廠子類必須實現的介面,這樣進一步抽象化的好處是使得工廠方法模式可以使系統在不修改具體工廠角色 ...
  • saltstack遠程執行 安裝完Saltstack後可以立即執行shell命令,更新軟體包並將文件同時分不到所有受管系統。所有回覆都以一致的可配置格式返回。遠程執行參考文檔:http://docs.saltstack.cn/topics/tutorials/modules.html Salt命令的 ...
  • 最近做一個項目是關於採集指紋的系統,先給大家簡單介紹一下項目的主要功能: 該項目主要是做一個採集嬰幼兒的手掌指紋和掌紋的客戶端,並且通過服務端介面保存手掌指紋到阿裡雲oss存儲中。 同時後臺提供管理功能,對採集人員,系統角色許可權管理,同時提供嬰幼兒的手指指紋圖片的查看和分析功能。 系統分為三個子系統 ...
  • 什麼是微服務 簡而言之 : 微服務架構風格這種開發方法,是以開發一組小型服務的方式來開發一個獨立的應用系統的.其中每個小型服務都運行在自己的進行中,並經常採用HTTP資源API 這樣輕量的機制來相互通信.這些服務圍繞業務功能進行構建,並能通過全自動的部署機制來進行獨立部署.這些微服務可以使用不同的語 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...