1. 程式人生 > >設計模式——命令模式(遙控器與燈)

設計模式——命令模式(遙控器與燈)

本文首發於cdream的個人部落格,點選獲得更好的閱讀體驗!

歡迎轉載,轉載請註明出處。

本文主要對命令模式進行概述講解,並使用使用遙控器與燈來講述命令模式中呼叫者與接收者的關係。

image-20181213000538037

一、概述

命令模式(英語:Command pattern)是一種設計模式,它嘗試以物件來代表實際行動。命令物件可以把行動(action) 及其引數封裝起來,於是這些行動可以被重複使用、撤銷、撤銷後重做。

這個是概念是來自維基百科,我覺得最容易理解,就是把命令封裝成物件,使命令可以重複呼叫、撤銷,降低了呼叫者和接受者的耦合,同時容易擴展出新的命令。

其他描述
1.將“請求”封裝成物件,以便使不同的請求、佇列或日誌來引數化其他物件,命令模式也支援可撤銷操作。(Head First 設計模式)
2.命令模式把一個請求或者操作封裝到一個物件中。命令模式允許系統使用不同的請求把客戶端引數化,對請求排隊或者記錄請求日誌,可以提供命令的撤銷和恢復功能。(Java與模式)

大家做適當參考,不理解可以先看看下面的原始碼!

二、結構

如圖,這是命令模式的結構:

image-20181212211929846

以用遙控器開燈為例(Head First 設計模式例子)

  • Invoker:命令呼叫者,負責呼叫命令物件的請求——遙控器
  • Command:聲明瞭一個具體命令類實現的介面——命令的介面
  • ConcreteCommand:具體命令,實現了Invoker和Receiver之間的解耦,通常持有接收者物件的飲用——開燈按鈕執行的命令

  • Receiver:命令接收者,真正接收命令並執行動作的物件——
  • Client:客戶端,創造具體的命令,並確定接收者

三、原始碼

Receiver——二檔亮度的燈

// 二檔調節的燈,在這裡作為接收者
public class Light {
    public static final String HIGH = "賊亮";

    public static final String MEDIUM = "有點亮";

    public static final String LOW = "快滅火了";

    public static final String OFF = "真的滅火了~";

    private String luminance;

    public Light() {
        this.luminance = OFF;
    }

    public void off() {
        System.out.println("燈關閉了");
        this.luminance = OFF;
    }

    public void high() {
        System.out.println("賊亮");
        this.luminance = HIGH;
    }

    public void medium() {
        System.out.println("挺亮地!");
        this.luminance = MEDIUM;
    }

    public void low() {
        System.out.println("快滅火了");
        this.luminance = LOW;
    }
    
    public String getLuminance(){
        return this.luminance;
    }
}

Command——做個命令介面,帶撤銷功能

public interface Command {
    void excute();
    void undo();
}

LightHighCommand——高光命令

public class LightHighCommand implements Command {
    private Light light;
    private String preLuminance;
    @Override
    public void excute() {
        // 備份上一個命令,撤銷使用
        preLuminance = light.getLuminance();
        light.high();
    }

    @Override
    public void undo() {
        if (Light.HIGH.equals(preLuminance)){
            light.high();
        }else if(Light.MEDIUM.equals(preLuminance) ){
            light.medium();
        }else if (Light.LOW.equals(preLuminance)){
            light.low();
        }else if (Light.OFF.equals(preLuminance)){
            light.off();
        }
    }

    public LightHighCommand(Light light) {
        this.light = light;
    }
    // 智慧遙控器按鍵功能可以控制多個燈
    // 如果控制令一個接收者,可以傳入,不用新建命令
    public void setLight(Light light) {
        this.light = light;
    }
}

LightOffCommand——關燈命令

public class LightOffCommand implements Command {
    private Light light;
    private String preLuminance;

    public LightOffCommand(Light light) {
        this.light = light;
    }

    @Override
    public void excute() {
        preLuminance = light.getLuminance();
        light.off();
    }

    @Override
        public void undo() {
            if (Light.HIGH.equals(preLuminance)){
                light.high();
            }else if(Light.MEDIUM.equals(preLuminance) ){
                light.medium();
            }else if (Light.LOW.equals(preLuminance)){
                light.low();
            }else if (Light.OFF.equals(preLuminance)){
                light.off();
            }
        }
    
    public void setLight(Light light) {
        this.light = light;
    }
}

Invoker——遙控器

public class RemoteControl {
    private Command off;
    private Command high;
    private Command medium;
    private Command low;
    private Command preCommand;

    public void setOff(Command off) {
        this.off = off;

    }

    public void setHigh(Command high) {
        this.high = high;
    }

    public void setMedium(Command medium) {
        this.medium = medium;
    }

    public void setLow(Command low) {
        this.low = low;

    }

    public void lightOff() {
        off.excute();
        preCommand = off;
    }

    public void lightHigh() {
        high.excute();
        preCommand = high;

    }

    public void lightMedium() {
        medium.excute();
        preCommand = medium;

    }

    public void lightLow() {
        low.excute();
        preCommand = low;
    }

    public void undo() {
        if (preCommand == null) {
            System.out.println("無法撤銷");
        } else {
            preCommand.undo();
        }
    }
}

Client——客戶端

public class Client {
    public static void main(String[] args) {
        // 建立接收者
        Light light = new Light();
        // 建立命令
        Command lightHighCommand = new LightHighCommand(light);
        Command lightOffCommand = new LightOffCommand(light);
        // 建立呼叫者
        RemoteControl remoteControl = new RemoteControl();
        remoteControl.setHigh(lightHighCommand);
        remoteControl.setOff(lightOffCommand);
        // 呼叫
        remoteControl.lightHigh();
        remoteControl.lightOff();
        remoteControl.undo();
    }
}

————————>結果
賊亮
燈關閉了
賊亮

以上就是一個帶撤回功能的命令模式,其中:

如果想實現多步撤回,可以考慮把呼叫者中的preCommand換成stack;

如果想實現組合命令,可以重新建立一個巨集命令,如下

public class MacroCommand implements Command {
    private Command[] commands;

    public MacroCommand(Command[] commands) {
        this.commands = commands;
    }

    @Override
    public void excute() {
        for (Command command : commands) {
            command.excute();
        }
    }

    @Override
    public void undo() {
        for (Command command : commands) {
            command.undo();
        }
    }
}

你可以用這個命令實現任何形式的命令組合,甚至如果你覺得你的遙控器可以控制空調,控制電源,控制電飯鍋,你甚至可以把這些命令組合進來~

四、命令模式的優缺點

優點

  • 解耦合:將命令呼叫者和命令執行者通過命令進行解耦,命令呼叫者不關心由誰來執行命令,只要命令執行就可以

  • 更動態的控制:請求被封裝成物件後,可以輕易的引數化、佇列化、日誌化,使系統更加靈活。
  • 更容易的命令組合:有了巨集命令後,可以任意的對命令進行組合
  • 更好擴充套件性:可以輕易的新增新的命令,並不會影響到其他的命令

缺點

  • 命令過多時,會建立了過多的命令類,不方便進行管理

五、總結

本文對命令模式作了簡單的介紹,命令模式只要明白呼叫者如何通過命令與接收者互動,就比較好理解。在實際應用中,命令模式可以用在並行處理、事務行為、執行緒池等地方。例如傳統的執行緒池就有addTask()方法將命令加入到等待被執行的佇列中,允許多執行緒執行實現java.lang.Runnable 的命令,儘管執行緒池本身對具體的任務毫無認知。


  1. Head First 設計模式,Eric Freeman &Elisabeth Freeman with Kathy Sierra & Bert Bates
  2. Command pattern,wiki
  3. 《JAVA與模式》之命令模式