1. 程式人生 > 實用技巧 >《Head First 設計模式》:命令模式

《Head First 設計模式》:命令模式

正文

一、定義

命令模式將“請求”封裝成物件(命令物件),以便使用不同的“請求”來引數化其他物件。

要點:

  • 命令模式可將“動作的請求者”從“動作的執行者”物件中解耦。
  • 被解耦的兩者之間通過命令物件進行溝通。命令物件封裝了接收者和一個或多個動作。
  • 命令物件提供一個 execute() 方法,該方法封裝了接收者的動作。當此方法被呼叫時,接收者就會執行這些動作。
  • 呼叫者持有一個或多個命令物件,通過呼叫命令物件的 execute() 方法發出請求,這會使得接收者的動作被呼叫。

二、實現步驟

1、建立接收者類

接收者類中包含要執行的動作。

(1)接收者A

/**
 * 接收者A
 */
public class ReceiverA {
    
    /**
     * 動作1
     */
    public void action1() {
        System.out.println("ReceiverA do action1");
    }
    
    /**
     * 動作2
     */
    public void action2() {
        System.out.println("ReceiverA do action2");
    }
}

(2)接收者B

/**
 * 接收者B
 */
public class ReceiverB {

    /**
     * 動作1
     */
    public void action1() {
        System.out.println("ReceiverB do action1");
    }
    
    /**
     * 動作2
     */
    public void action2() {
        System.out.println("ReceiverB do action2");
    }
}

2、建立命令介面

/**
 * 命令介面
 */
public interface Command {

    /**
     * 執行命令
     */
    public void execute();
}

3、建立具體的命令,並實現命令介面

命令物件中,封裝了命令接收者和相關動作。

(1)命令A1

/**
 * 命令A1
 */
public class ConcreteCommandA1 implements Command{
    
    /**
     * 接收者A
     */
    ReceiverA receive;
    
    public ConcreteCommandA1(ReceiverA receive) {
        this.receive = receive;
    }

    @Override
    public void execute() {
        // 接收者A執行動作1
        receive.action1();
    }
}

(2)命令A2

/**
 * 命令A2
 */
public class ConcreteCommandA2 implements Command{
    
    /**
     * 接收者A
     */
    ReceiverA receive;
    
    public ConcreteCommandA2(ReceiverA receive) {
        this.receive = receive;
    }

    @Override
    public void execute() {
        // 接收者A執行動作2
        receive.action2();
    }
}

(3)命令B1

/**
 * 命令B1
 */
public class ConcreteCommandB1 implements Command{
    
    /**
     * 接收者B
     */
    ReceiverB receive;
    
    public ConcreteCommandB1(ReceiverB receive) {
        this.receive = receive;
    }

    @Override
    public void execute() {
        // 接收者B執行動作1
        receive.action1();
    }
}

(4)命令B2

/**
 * 命令B2
 */
public class ConcreteCommandB2 implements Command{
    
    /**
     * 接收者B
     */
    ReceiverB receive;
    
    public ConcreteCommandB2(ReceiverB receive) {
        this.receive = receive;
    }

    @Override
    public void execute() {
        // 接收者B執行動作2
        receive.action2();
    }
}

4、建立呼叫者

呼叫者通過持有的命令物件,來呼叫接收者的動作。

/**
 * 呼叫者
 */
public class Invoker {

    /**
     * 命令物件
     */
    Command command;
    
    /**
     * 設定命令
     */
    public void setCommand(Command command) {
        this.command = command;
    }
    
    /**
     * 呼叫動作
     */
    public void invoke() {
        command.execute();
    }
}

5、呼叫者執行命令

public class Test {
    
    public static void main(String[] args) {
        // 命令呼叫者
        Invoker invoker = new Invoker();
        
        // 命令接收者
        ReceiverA receiverA = new ReceiverA();
        ReceiverB receiverB = new ReceiverB();
        
        // 建立命令,並指定接收者
        Command commandA1 = new ConcreteCommandA1(receiverA);
        Command commandA2 = new ConcreteCommandA2(receiverA);
        Command commandB1 = new ConcreteCommandB1(receiverB);
        Command commandB2 = new ConcreteCommandB2(receiverB);
        
        // 呼叫者執行命令
        invoker.setCommand(commandA1);
        invoker.invoke();
        invoker.setCommand(commandA2);
        invoker.invoke();
        invoker.setCommand(commandB1);
        invoker.invoke();
        invoker.setCommand(commandB2);
        invoker.invoke();
    }
}

三、舉個栗子

1、背景

假設你要設計一個家電自動化遙控器的 API。這個遙控器具有七個可程式設計的插槽(每個都可以指定到一個不同的家電裝置),每個插槽都有對應的“開”、“關”按鈕。

你希望每個插槽都能夠控制一個或一組裝置。並且,這個遙控器適用於目前的裝置和任何未來可能出現的裝置。

2、實現

(1)建立家電類

/**
 * 電燈(接收者)
 */
public class Light {
    
    /**
     * 電燈位置
     */
    String location;
    
    public Light(String location) {
        this.location = location;
    }
    
    /**
     * 開燈
     */
    public void on() {
        System.out.println(location + " light is on");
    }
    
    /**
     * 關燈
     */
    public void off() {
        System.out.println(location + " light is off");
    }
}
/**
 * 音響(接收者)
 */
public class Stereo {
    
    /**
     * 開啟音響
     */
    public void on() {
        System.out.println("Stereo is on");
    }
    
    /**
     * 關閉音響
     */
    public void off() {
        System.out.println("Stereo is off");
    }
    
    /**
     * 設定為播放CD
     */
    public void setCd() {
        System.out.println("Stereo is set for CD input");
    }
    
    /**
     * 設定為播放DVD
     */
    public void setDvd() {
        System.out.println("Stereo is set for DVD input");
    }
    
    /**
     * 設定音量
     */
    public void setVolume(int volume) {
        System.out.println("Stereo volume set to " + volume);
    }
}

(2)建立命令介面

/**
 * 命令介面
 */
public interface Command {

    /**
     * 執行命令
     */
    public void execute();
}

(3)建立具體的命令,並繼承命令介面

/**
 * 無命令
 */
public class NoCommand implements Command {
    
    @Override
    public void execute() {}
}
/**
 * 開燈命令
 */
public class LightOnCommand implements Command {
    
    Light light;
    
    public LightOnCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.on();
    }
}
/**
 * 關燈命令
 */
public class LightOffCommand implements Command {
    
    Light light;
    
    public LightOffCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.off();
    }
}
/**
 * 開啟音響播放CD命令
 */
public class StereoOnWithCDCommand implements Command {
    
    Stereo stereo;
    
    public StereoOnWithCDCommand(Stereo stereo) {
        this.stereo = stereo;
    }

    @Override
    public void execute() {
        stereo.on();
        stereo.setCd();
        stereo.setVolume(11);
    }
}
/**
 * 關閉音響命令
 */
public class StereoOffCommand implements Command {
    
    Stereo stereo;
    
    public StereoOffCommand(Stereo stereo) {
        this.stereo = stereo;
    }

    @Override
    public void execute() {
        stereo.off();
    }
}

(4)建立遙控器類

/**
 * 遙控器(呼叫者)
 */
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;
    }
    
    /**
     * “開”按鈕被按下
     */
    public void onButtonWasPushed(int slot) {
        // 執行開命令
        onCommands[slot].execute();
    }
    
    /**
     * “關”按鈕被按下
     */
    public void offButtonWasPushed(int slot) {
        // 執行關命令
        offCommands[slot].execute();
    }
}

(5)使用遙控器控制家電

public class Test {
    
    public static void main(String[] args) {
        // 遙控器
        RemoteControl remoteControl = new RemoteControl();
        
        // 家電
        Light livingRoomLight = new Light("Living Room");
        Light kitchenLight = new Light("Kitchen");
        Stereo stereo = new Stereo();
        
        // 建立命令,並指定家電
        Command livingRoomLightOn = new LightOnCommand(livingRoomLight);
        Command livingRoomLightOff = new LightOffCommand(livingRoomLight);
        Command kitchenLightOn = new LightOnCommand(kitchenLight);
        Command kitchenLightOff = new LightOffCommand(kitchenLight);
        Command stereoOnWithCD = new StereoOnWithCDCommand(stereo);
        Command stereoOff = new StereoOffCommand(stereo);
        
        // 設定插槽命令
        remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
        remoteControl.setCommand(1, kitchenLightOn, kitchenLightOff);
        remoteControl.setCommand(2, stereoOnWithCD, stereoOff);
        
        // 遙控器執行命令
        remoteControl.onButtonWasPushed(0);
        remoteControl.offButtonWasPushed(0);
        remoteControl.onButtonWasPushed(1);
        remoteControl.offButtonWasPushed(1);
        remoteControl.onButtonWasPushed(2);
        remoteControl.offButtonWasPushed(2);
    }
}

四、命令模式的更多用途

1、佇列請求

命令可以將運算塊打包(一個接收者和一組動作),然後將它傳來傳去,就像是一般的物件一樣。即使在命令物件被建立許久之後,運算依然可以被呼叫。事實上,它甚至可以在不同的執行緒中被呼叫。

因此,命令模式可以用來實現佇列請求。具體做法是:在工作佇列的一端新增命令,另一端通過執行緒取出命令,呼叫它的 execute() 方法,等呼叫完成後,將此命令物件丟棄,再取出下一個命令……

2、日誌請求

某些應用需要我們將所有的動作都記錄在日誌中,並能在系統宕機後,重新呼叫這些動作恢復到之前的狀態。

命令模式能夠支援這一點。具體做法是:當我們執行命令時,利用序列化將命令物件儲存在磁碟中。一旦系統宕機,再利用反序列化將命令物件重新載入,並依次呼叫這些物件的 execute() 方法。