Head First 設計模式之命令模式(Java例子)

前言:

來源於《head first 設計模式》。當作讀書筆記了,這次看的是第六章裝命令模式。

命令模式的概念

將請求封裝成對象,這可以讓你使用不同的請求、隊列、或者日誌請求來參數化其他對象,命令模式也支持撤銷操作,當需要將發出請求和執行請求的對象解耦的時候,使用命令模式即可。

命令模式的uml圖

在這裏插入圖片描述
簡單介紹一下:

  • 命令(Command):爲所有命令聲明瞭一個接口。調用命令對象的 execute()方法,就可以讓接收者進行相關的操作。
  • 具體命令(ConcreteCommand):實現命令接口,定義了動作和接收者之間的綁定關係。調用者只要調用 execute() 就可以發出請求,然後由 ConcreteCommand 調用接收者的一個或多個動作。
  • 請求者(Invoker):持有一個命令對象或者多個命令對象,有一個行動方法,在某個時間點調用命令對象的 execute() 方法,將請求付諸實行。
  • 接收者(Receiver):接收者知道如何進行必要的動作,實現這個請求。任何類都可以當接收者。這個可以看作是最終的執行者
  • 客戶端(Client):創建一個具體命令(ConcreteCommand)對象並確定其接收者(Receiver),包括把其他角色串連在一起。

看個例子把。

模擬一個遙控器,控制燈的開關.

定義接受者

這是最後執行動作的角色。

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");
	}
}
command接口
public interface Command {
	/**
     * 執行命令
     */
    void execute();

    /**
     * 撤銷命令
     */
    void undo();
}
ConcreteCommand具體命令類

具體的兩個命令,分別定義其撤銷操作
關燈命令

public class LightOffCommand implements Command {
	Light light;
 
	public LightOffCommand(Light light) {
		this.light = light;
	}
 
	public void execute() {
		light.off();
	}
	@Override
    public void undo() {
        light.on();
    }
}

開燈命令

public class LightOnCommand implements Command {

    private Light light;

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

    @Override
    public void execute() {
        light.on();
    }

    @Override
    public void undo() {
        light.off();
    }
}
請求者(Invoker)

onCommands offCommands用於存儲命令對象,通過onButtonWasPushed來調用命令的execute 命令中再把處理交給接受者執行後面的操作

public class RemoteControl {
	
	Command[] onCommands;
	Command[] offCommands;
 	/**
     * 撤銷(回退)命令
     */
    private Command undoCommand;
	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();
		undoCommand=onCommands[slot];
	}
 
	public void offButtonWasPushed(int slot) {
		offCommands[slot].execute();
		undoCommand=onCommands[slot];
	}
    public void undoButton() {
        undoCommand.undo();
    }
	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();
	}
}

客戶端(遙控器)

前面,我們定義好了請求者、接收者已經兩者之間的聯繫中介 —— 命令對象。但是這幾個角色對象之間都是鬆耦合的,還沒有一個具體動作的流程,現在我們利用客戶端角色把整個動作流程串聯在一起。

public class RemoteLoader {
 
	public static void main(String[] args) {
		RemoteControl remoteControl = new RemoteControl();
 
		Light livingRoomLight = new Light("Living Room");

		LightOnCommand livingRoomLightOn = 
				new LightOnCommand(livingRoomLight);
		LightOffCommand livingRoomLightOff = 
				new LightOffCommand(livingRoomLight);	
		remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
		System.out.println(remoteControl);
		remoteControl.onButtonWasPushed(0);
		remoteControl.offButtonWasPushed(0);
		remoteControl.undoButton();
		}
}

總結

命令模式將發出請求的對象和執行請求的對象解耦,在被解耦的兩者之間是通過命令對象進行溝通的。

  • 一個命令對象通過在特定接收者上綁定一組動作來封裝一個請求。要達到這一點,命令對象將接收者和動作封裝進對象中,這個對象只暴露出一個 execute() 方法,當此方法被調用時,接收者就會進行這些動作。從外面來看,其他對象不知道究竟哪個接收者進行了哪些操作,只知道如果調用 execute() 方法,請求的目的就可以達到。
  • 當你不想返回一個有意義的對象時,空對象就很有用。這樣,我們就可以把處理 null 的責任轉移給空對象,甚至有些時候,空對象本身也被視爲一種設計模式。例如本例子中的noCommand
    適用場景:
     1、命令的發送者和命令執行者有不同的生命週期,命令發送了並不是立即執行。換言之,原先的請求發出者可能已經不在了,而命令對象本身仍然是活動的。這時命令的接收者可以是在本地,也可以在網絡的另外一個地址。命令對象可以在序列化之後傳送到另外一臺機器上去。
     2、命令需要進行各種管理邏輯,比如:對多個命令的統一控制。
     3、需要支持撤消/重試操作。命令對象可以把狀態存儲起來,等到客戶端需要撤銷命令所產生的效果時,可以調用 undo()方法,把命令所產生的效果撤銷掉。命令對象還可以提供 redo()方法, 以供客戶端在需要時再重新實施命令效果。
命令模式的更多用途

命令模式的關鍵之處就是把請求封裝成爲對象,也就是命令對象(一個接收者和一組動作),然後將它傳來傳去,就像是一般的對象一樣。現在,即使在命令對象被創建許久之後,運算依然可以被調用。事實上,它甚至可以在不同的線程中被調用。我們可以利用這樣的特性衍生一些應用,例如:線程池、工作隊列、日誌請求等。

隊列請求
想象有一個工作隊列:你在某一端添加命令,然後另一端則是線程。線程進行下面的動作:從隊列中取出一個命令,調用它的execute()方法,等待這個調用完成,然後將此命令對象丟棄,再取出下一個命令…
請注意,工作隊列和命令對象之間是完全解耦的。此刻線程可能在進行財務運算,下一刻卻在讀取網絡數據。工作隊列對象不在乎到底做些什麼,它們只知道取出命令對象,然後調用其execute()方法。類似地,它們只要實現命令模式的對象,就可以放入隊列裏,當線程可用時,就調用此對象的execute()方法。
日誌請求
某些應用需要我們將所有的動作都記錄在日誌中,並能在系統死機之後,重新調用這些動作恢復到之前的狀態。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章