【設計模式(14)】行為型模式之命令模式
個人學習筆記分享,當前能力有限,請勿貶低,菜鳥互學,大佬繞道
如有勘誤,歡迎指出和討論,本文後期也會進行修正和補充
前言
生活中,一件事情的請求者和執行者不一定是同一個人。比如我們申請售後的時候,將需要的資訊等告知售後,然後由售後來安排處理,我們可以去做別的事情了;電視遙控器向電視傳送命令,電視來執行;老闆給我們分配任務等等。。。
好處是我們不需要將請求者與執行者建立繫結關係,也不需要佔用請求者的時間。
在開發中,我們也會需要解除請求者和執行者之間的耦合,一方面利於擴充套件,一方面使得兩者不必同步運作。
命令模式(Command Pattern)是一種資料驅動的設計模式,它屬於行為型模式。
請求以命令的形式包裹在物件中,並傳給呼叫物件。呼叫物件尋找可以處理該命令的合適的物件,並把該命令傳給相應的物件,該物件執行命令。
1.介紹
使用目的:將一個請求封裝為一個物件,使發出請求的責任和執行請求的責任分割開
使用時機:需要將方法的請求者與方法的實現者解耦,進而讓兩者之間通過命令物件進行溝通,方便將命令物件進行儲存、傳遞、呼叫、增加與管理。
解決問題:在某些場合,比如要對行為進行"記錄、撤銷/重做、事務"等處理,這種無法抵禦變化的緊耦合是不合適的
實現方法:請求者通過呼叫者,再由呼叫者來呼叫接受者執行命令。
應用例項:
- 下載管理器,請求者將請求傳送給管理器,由管理器來執行並管理下載任務
- 命令池,請求者將命令組裝好後傳入命令池,由命令池執行並管理下載任務
優點:
- 降低了系統耦合度
- 新的命令可以很容易新增到系統中
- 釋放了請求者,使其不必等待執行結果
缺點:使用命令模式可能會導致某些系統有過多的具體命令類
2.結構
命令模式包含以下主要角色
- 抽象命令類(Command)角色:宣告執行命令的介面,擁有執行命令的抽象方法 execute()。
- 具體命令(Concrete Command)角色:是抽象命令類的具體實現類,它擁有接收者物件,並通過呼叫接收者的功能來完成命令要執行的操作。
- 實現者/接收者(Receiver)角色:執行命令功能的相關操作,是具體命令物件業務的真正實現者。
- 呼叫者/請求者(Invoker)角色:是請求的傳送者,它通常擁有很多的命令物件,並通過訪問命令物件來執行相關請求,它不直接訪問接收者。
- 客戶端呼叫
invoker
invoker
持有Commond
物件,並執行execute()
命令ConcreteCommand
需要實現Commond
介面,並持有Receiver
物件,在execute()
方法中呼叫Receiver
則需要定義相關的方法,以供ConcreteCommand
呼叫
3.實現步驟
-
定義接收者
class ReceiverA { public void action() { System.out.println("執行方法A"); } } class ReceiverB { public void action() { System.out.println("執行方法B"); } }
接收者裡面負責定義具體需要執行的相關方法
-
定義抽象命令類
interface Command { void execute(); }
用於規範命令類提供的方法介面
-
定義具體命令類
class ConcreteCommandA implements Command { private ReceiverA receiver; public ConcreteCommandA() { receiver = new ReceiverA(); } @Override public void execute() { receiver.action(); } } class ConcreteCommandB implements Command { private ReceiverB receiver; public ConcreteCommandB() { receiver = new ReceiverB(); } @Override public void execute() { receiver.action(); } }
此類為核心,需要在此處實現抽象命令類,並持有接收者,並定義命令執行的邏輯
-
定義呼叫者
class Invoker { private Command command; public void setCommand(Command command) { this.command = command; } public void call() { if (null != command) { command.execute(); } } }
持有命令物件,並提供設定和執行命令的方法
完整程式碼
package com.company.test.command;
class ReceiverA {
public void action() {
System.out.println("執行方法A");
}
}
class ReceiverB {
public void action() {
System.out.println("執行方法B");
}
}
interface Command {
void execute();
}
class ConcreteCommandA implements Command {
private ReceiverA receiver;
public ConcreteCommandA() {
receiver = new ReceiverA();
}
@Override
public void execute() {
receiver.action();
}
}
class ConcreteCommandB implements Command {
private ReceiverB receiver;
public ConcreteCommandB() {
receiver = new ReceiverB();
}
@Override
public void execute() {
receiver.action();
}
}
class Invoker {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void call() {
if (null != command) {
command.execute();
}
}
}
public class CommandTest {
public static void main(String[] args) {
Invoker invoker = new Invoker();
Command commandA = new ConcreteCommandA();
invoker.setCommand(commandA);
invoker.call();
Command commandB = new ConcreteCommandB();
invoker.setCommand(commandB);
invoker.call();
}
}
執行結果
4.擴充套件實戰
目標需求如下:
-
設計一個命令池,被多個命令類共享
-
客戶端呼叫請求者的方法,由請求者組裝命令,並交付給命令池
-
命令池會按順序執行接收到的命令
- 命令按照目標不同進行分組
- 同一組命令按照先後執行,如果某個命令執行時間超過2s則不再等待,執行下一條命令
- 每條命令均需要回調,即便超時
實際可能的業務場景如下
專案需要向多個裝置下發命令,通過同一個的命令池管理
對於同一個裝置的命令需要按順序執行,若某條命令完成或者超時則執行下一條,但每條命令均必須有回撥
因此不考慮超時的情況下,同一裝置的命令為堵塞佇列
對於不同裝置,之間的命令不能堵塞,不能相互影響
系統內的命令只負責呼叫裝置,具體裝置業務由裝置自己執行
測試客戶端程式碼:
public class CommandPoolTest {
public static void main(String[] args) {
CommandPool commandPool = new CommandPool();
CommandInterface command_1 = new CommandImpl(commandPool);
command_1.doAB();
command_1.doABC();
while(true){}
}
}
- 一共下發兩條命令,分別是
AB
和ABC
,那麼全部子命令應該是ABABC
- 系統中任務等待時間為1秒,超時則執行下一條命令
- 程式碼中命令
A
和C
都是瞬時任務,B
為持續3秒的任務,因此B
會超時 - 系統中我們添加了兩個目標,因此每個命令都會發送到這兩個目標
部分執行結果:
- 第一條命令
A
正常執行,並得到反饋 - 第二條命令
B
下發,但未得到反饋(超時) - 第三條命令
A
下發,並得到反饋
完整的執行結果中,下發命令的順序是ABABC
,而收到反饋的順序是AACBB
,因為兩次B
都是超時完成
完整Demo地址:https://gitee.com/echo_ye/practice/tree/master/src/main/java/com/company/commandPool
後記
設計模式只是提供了一種解決某個問題的思路,在實際開發中往往需要多種設計模式協同使用,並且需要根據需求擴充套件和變型
比如這個demo,初略看了一下,使用了命令模式、享元模式、代理模式、單例模式等,還有我也分不清楚了。。。
總之,最終目的就是實現我們的需求,而設計模式則提供瞭解決的思路
作者:Echo_Ye
WX:Echo_YeZ
Email :[email protected]
個人站點:在搭了在搭了。。。(右鍵 - 新建資料夾)