設計模式之命令模式

王若伊_恩赐解脱發表於2024-09-03

命令模式(Command Pattern),給大家的第一感覺,就是給程式傳送命令,比如:啟動、暫停,然後程式根據接收到的命令直接執行就行。
這樣的理解相對來說比較狹義,來看下命令模式官方的定義:
將一個請求封裝為一個物件,使發出請求的責任和執行請求的責任分割開。這樣兩者之間透過命令物件進行溝通,這樣方便將命令物件進行儲存、傳遞、呼叫、增加與管理。
一個太狹義,一個又太晦澀。(防盜連線:本文首發自http://www.cnblogs.com/jilodream/ )
我對命令模式的理解是這樣的:我們將請求引數,以及請求的執行邏輯、依賴物件等,封裝在一個物件中,將這個物件推送到執行的引擎中,由執行引擎來驅動執行。我們透過命令模式可以更好的封裝邏輯、管理邏輯。

它是物件導向的23種設計模式中的一種,屬於行為模式的範圍。
我們來看這樣的一個例子:

工作介面類

 1 package com.example.demo.learn.pattern.behavior.command;
 2 
 3 /**
 4  * @discription
 5  */
 6 public interface IWorkCommand {
 7 
 8     String getWorkName();
 9 
10     void execute();
11 }

司機工作類

 1 package com.example.demo.learn.pattern.behavior.command;
 2 
 3 import lombok.AllArgsConstructor;
 4 import lombok.extern.slf4j.Slf4j;
 5 
 6 /**
 7  * @discription
 8  */
 9 @AllArgsConstructor
10 @Slf4j
11 public class DriverWork implements IWorkCommand {
12 
13     private String name;
14 
15     @Override
16     public String getWorkName() {
17         return "Driver: " + name;
18     }
19 
20     @Override
21     public void execute() {
22         log.warn("start invoke {} work", getWorkName());
23         log.warn("運送貨物到指定目的地");
24         log.warn("打掃汽車衛生");
25         log.warn("將汽車送回停車地點");
26     }
27 }

程式設計師工作類

 1 package com.example.demo.learn.pattern.behavior.command;
 2 
 3 import lombok.AllArgsConstructor;
 4 import lombok.extern.slf4j.Slf4j;
 5 
 6 /**
 7  * @discription
 8  */
 9 
10 @AllArgsConstructor
11 @Slf4j
12 public class ProgrammerWork implements IWorkCommand{
13 
14     private String name;
15     @Override
16     public String getWorkName() {
17         return "programmer: "+name;
18     }
19 
20     @Override
21     public void execute() {
22         log.warn("start invoke {} work", getWorkName());
23         log.warn("修復昨天遺留的問題.");
24         log.warn("完成今天的開發工作.");
25         log.warn("最佳化系統效能和穩定性.");
26     }
27 }

執行中心

 1 package com.example.demo.learn.pattern.behavior.command;
 2 
 3 /**
 4  * @discription
 5  */
 6 public class InvokeCenter {
 7     public void invokeWork(IWorkCommand workCommand) {
 8 
 9         workCommand.execute();
10     }
11 }

主類

 1 package com.example.demo.learn.pattern.behavior.command;
 2 
 3 /**
 4  * @discription
 5  */
 6 public class PatternMain {
 7     public static void main(String[] args) {
 8         IWorkCommand programmerWork = new ProgrammerWork("小p");
 9         IWorkCommand driverWork = new DriverWork("小d");
10         InvokeCenter invokeCenter = new InvokeCenter();
11         invokeCenter.invokeWork(driverWork);
12         invokeCenter.invokeWork(programmerWork);
13     }
14 }

輸出結果是這樣的:

Connected to the target VM, address: '127.0.0.1:52437', transport: 'socket'
15:57:07.856 [main] WARN com.example.demo.learn.pattern.behavior.command.DriverWork - start invoke Driver: 小d work
15:57:07.866 [main] WARN com.example.demo.learn.pattern.behavior.command.DriverWork - 運送貨物到指定目的地
15:57:07.866 [main] WARN com.example.demo.learn.pattern.behavior.command.DriverWork - 打掃汽車衛生
15:57:07.866 [main] WARN com.example.demo.learn.pattern.behavior.command.DriverWork - 將汽車送回停車地點
15:57:07.867 [main] WARN com.example.demo.learn.pattern.behavior.command.ProgrammerWork - start invoke programmer: 小p work
15:57:07.867 [main] WARN com.example.demo.learn.pattern.behavior.command.ProgrammerWork - 修復昨天遺留的問題.
15:57:07.868 [main] WARN com.example.demo.learn.pattern.behavior.command.ProgrammerWork - 完成今天的開發工作.
15:57:07.868 [main] WARN com.example.demo.learn.pattern.behavior.command.ProgrammerWork - 最佳化系統效能和穩定性.
Disconnected from the target VM, address: '127.0.0.1:52437', transport: 'socket'

Process finished with exit code 0

在這個例子中,我們並不直接執行各種具體的工作,而是將他們都封裝到一段方法中,由執行引擎統一的來執行。
這段邏輯是不是和透過例項化Thread 的方式,進行多執行緒操作的邏輯很像?(請參考這篇文章)
沒錯,在多執行緒的執行中,就應用到了命令模式。(防盜連線:本文首發自http://www.cnblogs.com/jilodream/ )
我們結合命令模式,可以發現這種結構大概有這3個角色:
1、一個抽象的命令介面(抽象類),在這裡是 ,他用來約定我們要執行的方法放在哪裡,怎麼執行。我們一般稱之為抽象命令類 Command
2、實現了抽象命令的實現類,在這裡是,我們一般透過編寫這個類,來實現我們想要的邏輯。我們一般稱之為具體命令類 Concrete Command
3、呼叫者,在這裡是,我們一般透過呼叫者來儲存和執行命令,一般也稱之為 請求者/呼叫者 invoker
除此之外還有一個角色
實現者(Receiver),這個角色,在我們的示例中隱藏的有點深,是log物件,也就是命令物件中,真正執行邏輯操作的物件。
注意呼叫者invoker 是不直接持有實現者的,兩者是沒有耦合關係的,是透過持有命令物件,間接的持有了呼叫者,間接的驅動了呼叫者。這樣做既可以讓呼叫者不關心具體的業務(譬如說執行緒池從來不直接持有執行物件的引用,而只持有對應的執行方法 (run()),由執行方法來組織邏輯和解耦)。
類圖大概是這樣子:

有些人問,我直接依賴呼叫者,然後呼叫呼叫者的某些方法,來實現我需要的邏輯是否可以,

答案是可以,但是如果第三方想要執行你的這段邏輯、或者你需要將這段邏輯交給第三方去在特定的時機處理執行,你會怎麼做呢?
這時候你就需要將你實現的這段邏輯封裝到一個物件中,交給其他人,這時候最終又變成了命令模式的體現。

命令模式除了進行解耦,還有一個好處就是可以編排和管理業務邏輯(命令)。
舉個例子:有時候我們要做的一個業務包含幾件相關的事,事情之間沒有先後順序,
比如我們去超市買一瓶可樂,需要做:
1、拿可樂
2、支付
此時我們就可以將每件事各自封裝成一個命令,將整個業務包含的命令,打包丟給執行引擎,這樣是不是就很好處理業務了。
A業務要做:a,b,c 三個命令
B業務要做:a,c,d,e 四個命令
我們只要定義每個命令,然後封裝好每塊業務需要做的幾件事(命令),這樣面相物件的設計感覺一下子就出來了。

相關文章