簡介
Encapsulate a request as an object,thereby letting you parameterize clients with different requests,queue or log requests,and support undoable operations.
將一個請求封裝成一個對象芦昔,從而讓你使用不同的請求把客戶端參數(shù)化天通,對請求排隊或者記錄請求日志雄卷,可以提供命令的撤銷和恢復(fù)功能。
命令模式(Command Pattern)是對命令的封裝郭蕉,每一個命令都是一個操作:請求的一方發(fā)出請求要求執(zhí)行一個操作疼邀;接收的一方收到請求,并執(zhí)行操作召锈。命令模式 解耦了請求方和接收方旁振,請求方只需請求執(zhí)行命令,不用關(guān)心命令是怎樣被接收,怎樣被操作以及是否被執(zhí)行····
軟件系統(tǒng)中规求,行為請求者與行為實現(xiàn)者通常是一種緊耦合關(guān)系筐付,因為這樣的實現(xiàn)簡單明了卵惦。但緊耦合關(guān)系缺乏擴展性阻肿,在某些場合中,當(dāng)需要為行為進行記錄沮尿,撤銷或重做等處理時丛塌,只能修改源碼。而 命令模式 通過為請求與實現(xiàn)間引入一個抽象命令接口畜疾,解耦了請求與實現(xiàn)赴邻,并且中間件是抽象的,它可以有不同的子類實現(xiàn)啡捶,因此其具備擴展性姥敛。
命令模式 本質(zhì):解耦命令請求與處理
主要解決
當(dāng)系統(tǒng)的某項操作具備命令語義時,且命令實現(xiàn)不穩(wěn)定(變化)瞎暑,那么可以通過 命令模式 解耦請求與實現(xiàn)彤敛,利用抽象命令接口使請求方代碼架構(gòu)穩(wěn)定,封裝接收方具體命令實現(xiàn)細(xì)節(jié)了赌。接收方與抽象命令接口呈現(xiàn)弱耦合(內(nèi)部方法無需一致)墨榄,具備良好的擴展性。
優(yōu)缺點
優(yōu)點
- 通過引入中間件(抽象接口)勿她,解耦了命令請求與實現(xiàn)袄秩;
- 擴展性良好,可以很容易地增加新命令逢并;
- 支持組合命令之剧,支持命令隊列;
- 可以在現(xiàn)有命令的基礎(chǔ)上砍聊,增加額外功能(比如日志記錄···背稼,結(jié)合 裝飾器模式 更酸爽);
缺點
- 具體命令類可能過多辩恼;
- 命令模式 的結(jié)果其實就是接收方的執(zhí)行結(jié)果雇庙,但是為了以命令的形式進行架構(gòu),解耦請求與實現(xiàn)灶伊,引入了額外類型結(jié)構(gòu)(引入了請求方與抽象命令接口)疆前,增加了理解上的困難(不過這也是設(shè)計模式帶來的一個通病,抽象必然會引入額外類型聘萨;抽象肯定比緊密難理解)竹椒;
使用場景
- 現(xiàn)實語義中具備 ”命令“ 的操作(如命令菜單,shell命令···)米辐;
- 需要將請求與實現(xiàn)解耦胸完;
- 需要支持命令的撤銷(Undo)操作和恢復(fù)(Redo)操作书释;
- 需要支持命令組合操作(宏命令);
模式講解
首先看下 命令模式 的通用 UML 類圖:
從 UML 類圖中赊窥,我們可以看到爆惧,命令模式 主要包含四種角色:
- 接收者角色(Receiver):該類負(fù)責(zé)具體實施或執(zhí)行一個請求;
- 命令角色(Command):定義需要執(zhí)行的所有命令行為锨能;
-
具體命令角色(ConcreteCommand):該類內(nèi)部維護一個 接收者(Receiver)扯再,在其
execut
方法中調(diào)用 Receiver 的相關(guān)方法; - 請求者角色(Invoker):接收客戶端的命令址遇,并執(zhí)行命令熄阻;
注:從 命令模式 的 UML 類圖中,其實可以很清晰地看出:Command
的出現(xiàn)就是作為Receiver
和Invoker
的中間件倔约,解耦了彼此秃殉。而之所以引入Command
中間件,我覺得是以下兩方面原因:
-
解耦請求與實現(xiàn):即解耦了
Invoker
和Receiver
浸剩,因為在 UML 類圖中钾军,Invoker
是一個具體的實現(xiàn),等待接收客戶端傳入命令(即Invoker
與客戶端耦合)乒省,Invoker
處于業(yè)務(wù)邏輯區(qū)域巧颈,應(yīng)當(dāng)是一個穩(wěn)定的結(jié)構(gòu)。而Receiver
是屬于業(yè)務(wù)功能模塊袖扛,是經(jīng)常變動的砸泛;如果沒有Command
,則Invoker
緊耦合Receiver
蛆封,一個穩(wěn)定的結(jié)構(gòu)依賴了一個不穩(wěn)定的結(jié)構(gòu)唇礁,就會導(dǎo)致整個結(jié)構(gòu)都不穩(wěn)定了。這也就是Command
引入的原因:不僅僅是解耦請求與實現(xiàn)惨篱,同時穩(wěn)定(Invoker
)依賴穩(wěn)定(Command
)盏筐,結(jié)構(gòu)還是穩(wěn)定的; -
擴展性增強:擴展性體現(xiàn)在兩個方面:1.
Receiver
屬于底層細(xì)節(jié)砸讳,可以通過更換不同的Receiver
達到不同的細(xì)節(jié)實現(xiàn)琢融;2.Command
接口本身就是抽象的,本身就具備擴展性簿寂;而且由于命令對象本身就具備抽象漾抬,如果結(jié)合 裝飾器模式,功能擴展簡直如魚得水常遂。
以下是 命令模式 的通用代碼:
class Client {
public static void main(String[] args) {
ICommand cmd = new ConcreteCommand();
Invoker invoker = new Invoker(cmd);
invoker.action();
}
//接收者
static class Receiver {
public void action() {
System.out.println("執(zhí)行具體操作");
}
}
//抽象命令接口
interface ICommand {
void execute();
}
//具體命令
static class ConcreteCommand implements ICommand {
// 直接創(chuàng)建接收者纳令,不暴露給客戶端
private Receiver mReceiver = new Receiver();
@Override
public void execute() {
this.mReceiver.action();
}
}
//請求者
static class Invoker {
private ICommand mCmd;
private Invoker(ICommand cmd) {
this.mCmd = cmd;
}
public void action() {
this.mCmd.execute();
}
}
}
注:在一個系統(tǒng)中,不同的命令對應(yīng)不同的請求,也就是說無法把請求抽象化平绩,因此 命令模式 中的Receiver
是具體實現(xiàn)圈匆;但是如果在某一個模塊中,可以對Receiver
進行抽象捏雌,其實這就變相使用到了 橋接模式(Command
類具備兩個變化的維度:Command
和Receiver
)跃赚,這樣子的擴展性會更加優(yōu)秀。
舉個例子
例子:假如現(xiàn)有我們有一個遙控器腹忽,可以控制風(fēng)扇的風(fēng)力大小来累,分為大砚作,中窘奏,小,關(guān)閉四個程度葫录,請使用程序進行實現(xiàn)着裹。
分析:上面的例子涉及兩個物體:遙控器和風(fēng)扇,直接的思路就是遙控器緊耦合風(fēng)扇米同,然后遙控器內(nèi)部暴露控制風(fēng)扇風(fēng)力等級接口骇扇。但是遙控器后續(xù)可能還可以對其他電器設(shè)備(如空調(diào)等)進行控制,因此有必要解耦遙控器和具體電器設(shè)備的緊密聯(lián)系面粮。對遙控器進行操作少孝,相當(dāng)于發(fā)出一個指令(命令),讓對應(yīng)的電器設(shè)備進行工作熬苍,那么 命令模式 是非常切合這個場景的稍走。
具體代碼如下:
class Client {
public static void main(String[] args) {
RemoteController remote = new RemoteController();
Fan fan = new Fan();
ICommand cmd = new TurnMinCommand(fan);
remote.action(cmd);
cmd = new TurnMidCommand(fan);
remote.action(cmd);
cmd = new TurnMaxCommand(fan);
remote.action(cmd);
cmd = new TurnOffCommand(fan);
remote.action(cmd);
}
//Receiver
static class Fan {
public void turnMin() {
System.out.println("Fan in Min degree");
}
public void turnMid() {
System.out.println("Fan in Mid degree");
}
public void turnMax() {
System.out.println("Fan in Max degree");
}
public void turnOff() {
System.out.println("Fan off");
}
}
//Command
interface ICommand {
void execute();
}
//ComcreteCommand
static class TurnMinCommand implements ICommand {
private Fan mFan;
public TurnMinCommand(Fan fan) {
this.mFan = fan;
}
@Override
public void execute() {
this.mFan.turnMin();
}
}
//ComcreteCommand
static class TurnMidCommand implements ICommand {
private Fan mFan;
public TurnMidCommand(Fan fan) {
this.mFan = fan;
}
@Override
public void execute() {
this.mFan.turnMid();
}
}
//ComcreteCommand
static class TurnMaxCommand implements ICommand {
private Fan mFan;
public TurnMaxCommand(Fan fan) {
this.mFan = fan;
}
@Override
public void execute() {
this.mFan.turnMax();
}
}
//ComcreteCommand
static class TurnOffCommand implements ICommand {
private Fan mFan;
public TurnOffCommand(Fan fan) {
this.mFan = fan;
}
@Override
public void execute() {
this.mFan.turnOff();
}
}
//Invoker
static class RemoteController {
public void action(ICommand cmd) {
cmd.execute();
}
}
}
由于遙控器已經(jīng)與具體電器解耦了,以后如果想擴展新命令柴底,只需增加即可婿脸,遙控器結(jié)構(gòu)無需改動。