設計模式——命令模式(遙控器與燈)
本文首發於cdream的個人部落格,點選獲得更好的閱讀體驗!
歡迎轉載,轉載請註明出處。
本文主要對命令模式進行概述講解,並使用使用遙控器與燈來講述命令模式中呼叫者與接收者的關係。
一、概述
命令模式(英語:Command pattern)是一種設計模式,它嘗試以物件來代表實際行動。命令物件可以把行動(action) 及其引數封裝起來,於是這些行動可以被重複使用、撤銷、撤銷後重做。
這個是概念是來自維基百科,我覺得最容易理解,就是把命令封裝成物件,使命令可以重複呼叫、撤銷,降低了呼叫者和接受者的耦合,同時容易擴展出新的命令。
其他描述:1.將“請求”封裝成物件,以便使不同的請求、佇列或日誌來引數化其他物件,命令模式也支援可撤銷操作。(Head First 設計模式)
2.命令模式把一個請求或者操作封裝到一個物件中。命令模式允許系統使用不同的請求把客戶端引數化,對請求排隊或者記錄請求日誌,可以提供命令的撤銷和恢復功能。(Java與模式)
大家做適當參考,不理解可以先看看下面的原始碼!
二、結構
如圖,這是命令模式的結構:
以用遙控器開燈為例(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
的命令,儘管執行緒池本身對具體的任務毫無認知。