小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

命令模式 Command 行為型 設(shè)計模式(十八)

 小樣樣樣樣樣樣 2020-07-18
命令模式(Command)
image_5c0f5d4c_1248
請分析上圖中這條命令的涉及到的角色以及執(zhí)行過程,一種可能的理解方式是這樣子的:
涉及角色為:大狗子和大狗子他媽
過程為:大狗子他媽角色 調(diào)用 大狗子的“回家吃飯”方法

引子

package command.origin;
public class BigDog {
public void goHomeForDinner() {
System.out.println("回家吃飯");
}
}
package command.origin;

public class BigDogMother {
public static void main(String[] args) {
BigDog bigDog = new BigDog();
bigDog.goHomeForDinner();
}
}
BigDog類擁有回家吃飯方法goHomeForDinner
BigDogMother作為客戶端調(diào)用BigDog的回家吃飯方法,完成了“大狗子回家吃飯”這個請求
上面的示例中,通過對命令執(zhí)行者的方法調(diào)用,完成了命令的下發(fā),命令調(diào)用者與命令執(zhí)行者之間是緊密耦合的
我們是否可以考慮換一種思維方式,將“你媽喊你回家吃飯”這一命令封裝成為一個對象?
不再是大狗子他媽調(diào)用大狗子的回家吃飯方法
而是大狗子他媽下發(fā)了一個命令,命令的內(nèi)容是“大狗子回家吃飯”
接下來是命令的執(zhí)行
這樣的話,“命令”就不再是一種方法調(diào)用了,在大狗子媽和大狗子之間多了一個環(huán)節(jié)---“命令”
 
看下代碼演變
BigDog 沒有變化
新增加了命令類Command  使用對象的接受者BigDog 進行初始化
命令的execute方法內(nèi)部調(diào)用接受者BigDog的方法
BigDogMother中下發(fā)了三個命令
然后逐個執(zhí)行這三個命令
package command.origin;
public class BigDog {
public void goHomeForDinner() {
System.out.println("回家吃飯");
}
}
package command.origin;
public class Command {
private BigDog bigDog;
Command(BigDog bigDog) {
this.bigDog = bigDog;
}
public void execute() {
bigDog.goHomeForDinner();
}
}
package command.origin;
public class BigDogMother {
public static void main(String[] args) {
BigDog bigDog = new BigDog();
Command command1 = new Command(bigDog);
Command command2 = new Command(bigDog);
Command command3 = new Command(bigDog);

command1.execute();
command2.execute();
command3.execute();
}
}
從上面的代碼示例中看到,通過對“請求”也就是“方法調(diào)用”的封裝,將請求轉(zhuǎn)變成了一個個的命令對象 
命令對象本身內(nèi)部封裝了一個命令的執(zhí)行者
好處是:命令可以進行保存?zhèn)鬟f了,命令發(fā)出者與命令執(zhí)行者之間完成了解耦,命令發(fā)出者甚至不知道具體的執(zhí)行者到底是誰
而且執(zhí)行的過程也更加清晰了

意圖

將一個請求封裝為一個對象,從而使可用不同的請求對客戶進行參數(shù)化;
對請求排隊或者記錄請求日志,以及支持可撤銷的操作。
別名 行為Action或者事物Transaction
命令模式就是將方法調(diào)用這種命令行為或者說請求 進一步的抽象,封裝為一個對象

結(jié)構(gòu)

上面的“大狗子你媽喊你回家吃飯”的例子只是展示了對于“命令”的一個封裝。只是命令模式的一部分。
下面看下命令模式完整的結(jié)構(gòu)
image_5c0f5d4c_60a2
命令角色Command
聲明了一個給所有具體命令類的抽象接口
做為抽象角色,通常是接口或者實現(xiàn)類
具體命令角色ConcreteCommand
定義一個接受者和行為之間的弱耦合關(guān)系,實現(xiàn)execute()方法
負(fù)責(zé)調(diào)用命令接受者的響相應(yīng)操作
請求者角色Invoker
負(fù)責(zé)調(diào)用命令對象執(zhí)行命令,相關(guān)的方法叫做行動action方法
接受者角色Receiver
負(fù)責(zé)具體實施和執(zhí)行一個請求,任何一個類都可以成為接收者
 
Command角色封裝了命令接收者并且內(nèi)部的執(zhí)行方法調(diào)用命令接收者的方法
也就是一般形如:
Command(Receiver receiver){
......
execute(){
receiver.action();
...
 
而Invoker角色接收Command,調(diào)用Command的execute方法
 
通過將“命令”這一行為抽象封裝,命令的執(zhí)行不再是請求者調(diào)用被請求者的方法這種強關(guān)聯(lián) ,而是可以進行分離
分離后,這一命令就可以像普通的對象一樣進行參數(shù)傳遞等

結(jié)構(gòu)代碼示例

command角色
package command;
public interface Command {
void execute();
}

ConcreateCommand角色
內(nèi)部擁有命令接收者,內(nèi)部擁有execute方法

package command;
public class ConcreateCommand implements Command {
private Receiver receiver;
ConcreateCommand(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
receiver.action();
}
}
 
Receiver命令接收者,實際執(zhí)行命令的角色
package command;

public class Receiver {
public void action(){
  System.out.println("command receiver do sth....");
 }
}
命令請求角色Invoker 用于處理命令,調(diào)用命令角色執(zhí)行命令
package command;
public class Invoker {
private Command command;
Invoker(Command command){
this.command = command;
}
void action(){
command.execute();
}
}
客戶端角色
package command;
public class Client {
public static void main(String[] args){
Receiver receiver = new Receiver();
Command command = new ConcreateCommand(receiver);
Invoker invoker = new Invoker(command);
invoker.action();
}
}
image_5c0f5d4c_13f0
 
在客戶端角色的測試代碼中,我們創(chuàng)建了一個命令,指定了接收者(實際執(zhí)行者)
然后將命令傳遞給命令請求調(diào)用者
雖然最終命令的接收者為receiver,但是很明顯如果這個Command是作為參數(shù)傳遞進來的
Client照樣能夠運行,他只需要借助于Invoker執(zhí)行命令即可
 
命令模式關(guān)鍵在于:引入命令類對方法調(diào)用這一行為進行封裝
命令類使的命令發(fā)送者與接收者解耦,命令請求者通過命令類來執(zhí)行命令接收者的方法
而不在是直接請求命名接收者

代碼示例

假設(shè)電視機只有三個操作:開機open 關(guān)機close和換臺change channel。
用戶通過遙控器對電視機進行操作。
 
電視機本身是命令接收者 Receiver
遙控器是請求者角色Invoker
用戶是客戶端角色Client
 
需要將用戶通過遙控器下發(fā)命令的行為抽象為命令類Command
Command有開機命令 關(guān)機命令和換臺命令
命令的執(zhí)行需要借助于命令接收者
Invoker 調(diào)用Command的開機命令 關(guān)機命令和換臺命令
 
電視類  Tv
package command.tv;

public class Tv {
public void turnOn(){
System.out.println("打開電視");
}

public void turnOff(){
System.out.println("關(guān)閉電視");
}
public void changeChannel(){
System.out.println("換臺了");
}
}
Command接口
package command.tv;
public interface Command {
void execute();
}
三個具體的命令類
內(nèi)部都保留著執(zhí)行者,execute方法調(diào)用他們的對應(yīng)方法
package command.tv;
 
public class OpenCommand implements Command {
 
private Tv myTv;
 
OpenCommand(Tv myTv) {
this.myTv = myTv;
}
 
@Override
public void execute() {
myTv.turnOn();
}
}
package command.tv;
 
public class CloseCommand implements Command {
 
private Tv myTv;
 
CloseCommand(Tv myTv) {
this.myTv = myTv;
}
 
@Override
public void execute() {
myTv.turnOff();
}
}
package command.tv;
 
public class ChangeChannelCommand implements Command {
 
private Tv myTv;
 
ChangeChannelCommand(Tv myTv) {
this.myTv = myTv;
}
 
@Override
public void execute() {
myTv.changeChannel();
}
}
遙控器Controller
擁有三個命令
package command.tv;
public class Controller {
private Command openCommand = null;
private Command closeCommand = null;
private Command changeChannelCommand = null;

public Controller(Command on, Command off, Command change) {
openCommand = on;
closeCommand = off;
changeChannelCommand = change;
}
 
public void turnOn() {
openCommand.execute();
}
 
public void turnOff() {
closeCommand.execute();
}
 
public void changeChannel() {
changeChannelCommand.execute();
}
}
用戶類User 
package command.tv;
public class User {
public static void main(String[] args) {
Tv myTv = new Tv();
OpenCommand openCommand = new OpenCommand(myTv);
CloseCommand closeCommand = new CloseCommand(myTv);
ChangeChannelCommand changeChannelCommand = new ChangeChannelCommand(myTv);
Controller controller = new Controller(openCommand, closeCommand, changeChannelCommand);
controller.turnOn();
controller.turnOff();
controller.changeChannel();
}
}

 

image_5c0f5d4c_25a0
以上示例將電視機的三種功能開機、關(guān)機、換臺 抽象為三種命令
一個遙控器在初始化之后,就可以擁有開機、關(guān)機、換臺的功能,但是卻完全不知道底層的實際工作的電視。
 

命令請求記錄

一旦將“發(fā)起請求”這一行為進行抽象封裝為命令對象
那么“命令”也就具有了一般對象的基本特性,比如,作為參數(shù)傳遞
比如使用容器存放進行存放
比如定義一個ArrayList  用于保存命令
ArrayList<Command> commands = new ArrayList<Command>();
這就形成了一個隊列
你可以動態(tài)的向隊列中增加命令,也可以從隊列中移除命令
你還可以將這個隊列保存起來,批處理的執(zhí)行或者定時每天的去執(zhí)行
你還可以將這些命令請求持久化到文件中,因為這些命令、請求 也不過就是一個個的對象而已

請求命令隊列

既然可以使用容器存放命令對象,我們可以實現(xiàn)一個命令隊列,對命令進行批處理
新增加一個CommandQueue類,內(nèi)部使用ArrayList存儲命令
execute()方法,將內(nèi)部的請求命令隊列全部執(zhí)行
package command;
import java.util.ArrayList;
 
public class CommandQueue {
 
private ArrayList<Command> commands = new ArrayList<Command>();
 
public void addCommand(Command command) {
commands.add(command);
}
 
public void removeCommand(Command command) {
commands.remove(command);
}
 
//執(zhí)行隊列內(nèi)所有命令
public void execute() {
for (Object command : commands) {
((Command) command).execute();
}
}
}
同時調(diào)整Invoker角色,使之可以獲得請求命令隊列,并且執(zhí)行命令請求隊列的方法
package command;
public class Invoker {
private Command command;
Invoker(Command command) {
this.command = command;
}
void action() {
command.execute();
}
//新增加命令隊列
private CommandQueue commandQueue;
public Invoker(CommandQueue commandQueue) {
this.commandQueue = commandQueue;
}
/*
* 新增加隊列批處理方法*/
public void batchAction() {
commandQueue.execute();
}
}
從上面的示意代碼可以看得出來,請求隊列的關(guān)鍵就是命令類
一旦創(chuàng)建了命令類,就解除了命令請求者與命令接收者之間耦合,就可以把命令當(dāng)做一個普通對象進行處理,調(diào)用他們的execute()執(zhí)行方法
 
所謂請求隊列不就是使用容器把命令對象保存起來,然后調(diào)用他們的execute方法嘛
所以說,命令請求的對象化,可以實現(xiàn)對請求排隊或者記錄請求日志的目的,就是命令對象的隊列

宏命令

計算機科學(xué)里的宏(Macro),是一種批量批處理的稱謂
一旦請求命令"對象化",就可以進行保存
上面的請求隊列就是如此,保存起來就可以實現(xiàn)批處理的功能,這就是命令模式的宏命令

撤銷操作

在上面的例子中,我們沒有涉及到撤銷操作
命令模式如何完成“撤銷”這一行為呢?
命令是對于請求這一行為的封裝抽象,每種ConcreteCommand都對應(yīng)者接收者一種具體的行為方式
所以想要能夠有撤銷的行為,命令接收者(最終的執(zhí)行者)必然需要有這樣一個功能
如果Receiver提供了一個rollback方法
也就是說如果一個receiver有兩個方法,action()和rollback()
當(dāng)執(zhí)行action方法后,調(diào)用rollback可以將操作進行回滾
那么,我們就可以給Command增加一個方法,recover() 用于調(diào)用receiver 的rollback方法
這樣一個命令對象就有了兩種行為,執(zhí)行execute和恢復(fù)recover
如果我們在每次的命令執(zhí)行后,將所有的 執(zhí)行過的 命令保存起來
當(dāng)需要回滾時,只需要逐個(或者按照執(zhí)行的相反順序)執(zhí)行命令對象的recover方法即可
這就很自然的完成了命令的撤銷行為,而且還可以批量進行撤銷
命令模式的撤銷操作依賴于命令接收者本身的撤銷行為,如果命令接收者本身不具備此類方法顯然沒辦法撤銷
另外就是依賴對執(zhí)行過的命令的記錄

使用場景

對于“大狗子你媽喊你回家吃飯”的例子,我想你也會覺得大狗子媽直接調(diào)用大狗子的方法就好了
脫褲子放屁,抽象出來一個命令對象有什么用呢?
 
對于簡單的方法調(diào)用,個人也認(rèn)為是自找麻煩
命令模式是有其使用場景以及特點的,并不是說不分青紅皂白的將請求處理都轉(zhuǎn)換為命令對象
 
到底什么情況需要使用命令模式?
通過上面的分析,如果你希望將請求進行排隊處理,或者請求日志的記錄
那么你就很可能需要命令模式,只有將請求轉(zhuǎn)換為命令對象,這些行為才更易于實現(xiàn)
 
如果系統(tǒng)希望支持撤銷操作
通過請求的對象化,可以方便的將命令的執(zhí)行過程記錄下來,就下來之后,就形成了“操作記錄”
擁有了操作記錄,如果有撤銷方法,就能夠執(zhí)行回滾撤銷
 
如果希望命令能夠被保存起來組成宏命令,重復(fù)執(zhí)行或者定時執(zhí)行等,就可以使用命令模式
 
如果希望將請求的調(diào)用者和請求的執(zhí)行者進行解耦,使得請求的調(diào)用者和執(zhí)行者并不直接接觸
命令對象封裝了命令的接收者,請求者只關(guān)注命令對象,根本不知道命令的接收者
 
如果希望請求具有更長的生命周期,普通方法調(diào)用,命令發(fā)出者和命令執(zhí)行者具有同樣的生命周期
命令模式下,命令對象封裝了請求,完成了命令發(fā)出者與命令接收者的解耦
命令對象創(chuàng)建后,只依賴命令接收者的執(zhí)行,只要命令接收者存在,就仍舊可以執(zhí)行,但是命令發(fā)出者可以消亡
 
總之命令模式的特點以及解決的問題,也正是他適用的場景
這一點在其他模式上也一樣
特點以及解決的問題,也正是他適用的場景,適用場景也正是它能解決的問題

總結(jié)

命令模式中對于場景中命令的提取,始終要注意它的核心“對接收者行為的命令抽象
比如,電視作為命令接收者,開機,關(guān)機,換臺是他自身固有的方法屬性,你的命令也就只能是與之對應(yīng)的開機、關(guān)機、換臺
你不能打游戲,即使你能打游戲,電視也不會讓你打游戲
這是具體的命令對象ConcreteCommand的設(shè)計思路
 
Command提供抽象的execute方法,所有的命令都是這個方法
調(diào)用者只需要執(zhí)行Command的execute方法即可,不關(guān)注到底是什么命令,命令接收者是誰
如果命令的接收者有撤銷的功能,命令對象就可以也同樣支持撤銷操作
關(guān)于如何抽取命令只需要記住:
命令模式中的命令對象是請求的封裝,請求基本就是方法調(diào)用,方法調(diào)用就是需要方法的執(zhí)行者,也就是命令的接收者有對應(yīng)行為的方法
 
請求者和接收者通過命令對象進行解耦,降低了系統(tǒng)的耦合度
命令的請求者Invoker與命令的接收者Receiver通過中間的Command進行連接,Command中的協(xié)議都是execute方法
所以,如果新增加命令,命令的請求者Invoker完全不需要做任何更改,他仍舊是接收一個Command,然后調(diào)用他的execute方法
具有良好的擴展性,滿足開閉原則
image_5c0f5d4c_5994
 
回到剛才說的,具體的命令對象ConcreteCommand的設(shè)計思路
需要與命令接收者的行為進行對應(yīng)
也就是針對每一個對請求接收者的調(diào)用操作,都需要設(shè)計一個具體命令類,可能會出現(xiàn)大量的命令類
有一句話說得好,“殺雞焉用宰牛刀”,所以使用命令模式一定要注意場景
以免被別人說脫褲子放屁,為了用設(shè)計模式而用設(shè)計模式....

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多