命令(Command)模式是指將請求封裝成為一個物件,使發出請求和執行請求的責任分割開,方便將命令物件進行儲存、傳遞、呼叫、增加與管理。
也就是將傳送者、接收者和呼叫命令封裝成獨立的物件,來供客戶端呼叫。屬於行為模式的一種。
一、命令模式介紹
命令模式將傳送者與接受者完全解耦,傳送者與接收者之間沒有直接的聯絡,傳送者只需要如何傳送請求,而不需要關心請求是如何完成的。下面就來看看命令模式的結構和實現:
1.1 命令模式的結構
將呼叫者和實現者進行分離,其結構如下所示:
Command
:抽象命令角色,宣告執行命令的介面Command1、Command2
:具體命令角色,是抽象命令角色的具體實現類ReceiverA、ReceiverB
:具體實現,具體命令物件的真正實現者Invoker
:呼叫者,處理命令、實現命令的具體操作者,負責對外提供命令服務Client
:客戶端
1.2 命令模式的實現
根據上面的結構圖,可以實現如下程式碼:
/**
* @description: 抽象命令類
* @author: wjw
* @date: 2022/4/5
*/
public interface Command {
public abstract void execute();
}
/**
* @description: 命令具體實現類1
* @author: wjw
* @date: 2022/4/5
*/
public class Command1 implements Command{
private ReceiverA receiverA = new ReceiverA();
@Override
public void execute() {
receiverA.action();
}
}
/**
* @description: 命令具體實現類2
* @author: wjw
* @date: 2022/4/5
*/
public class Command1 implements Command{
private ReceiverA receiverA = new ReceiverA();
@Override
public void execute() {
receiverA.action();
}
}
/**
* @description: 接收者類A
* @author: wjw
* @date: 2022/4/5
*/
public class ReceiverA {
public void action() {
System.out.println("我是ReceiverA");
}
}
/**
* @description: 具體實現者
* @author: wjw
* @date: 2022/4/5
*/
public class ReceiverB {
public void action() {
System.out.println("我是ReceiverB");
}
}
/**
* @description: 命令呼叫者
* @author: wjw
* @date: 2022/4/5
*/
public class Invoker {
private Command command;
public Invoker(Command command) {
this.command = command;
}
public void setCommand(Command command) {
this.command = command;
}
public void call() {
System.out.println("呼叫者執行命令command");
command.execute();
}
}
/**
* @description: 客戶端
* @author: wjw
* @date: 2022/4/5
*/
public class Client {
public static void main(String[] args) {
Command command1 = new Command1();
Invoker invoker1 = new Invoker(command1);
invoker1.call();
}
}
最後的客戶端執行結果為:
呼叫者執行命令command
我是ReceiverA
下面來看看命令模式的應用場景
二、命令模式的應用場景
2.1 Spring 框架中的 JdbcTemplate
本文選取的Spring版本是5.3.1,來看看JdbcTemplate
類中的query()
方法:
我們看到,上面的query()
方法中定義了一個內部類QueryStatementCallback
,並實現了StatementCallback
介面,點開檢視詳細內容:
@FunctionalInterface
public interface StatementCallback<T> {
//唯一的抽象方法
@Nullable
T doInStatement(Statement var1) throws SQLException, DataAccessException;
}
回到query()
方法中,我們發現最後返回的execute(new QueryStatementCallback())
中是將內部類QueryStatementCallback
當做引數進行返回。這裡QueryStatementCallback
就相當於命令模式中的具體命令物件,而StatementCallback
則是抽象命令物件。比如還有其他具體命令實現類,比如BatchUpdateStatementCallback
、ExecuteStatementCallback
等等:
看看execute()
方法,為了方便理解,程式碼做了精簡:
@Nullable
private <T> T execute(StatementCallback<T> action, boolean closeResources) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
Connection con = DataSourceUtils.getConnection(this.obtainDataSource());
Statement stmt = null;
Object var12;
try {
stmt = con.createStatement();
this.applyStatementSettings(stmt);
//執行doInStatement方法
T result = action.doInStatement(stmt);
this.handleWarnings(stmt);
//賦值為var12
var12 = result;
} catch (SQLException var10) {
//...
} finally {
//...
}
//最後返回statementCallback物件
return var12;
}
根據上面的程式碼,可以梳理整個執行流程:
實際上JdbcTemplate
這個類是呼叫者(Invoker)、實現者(Receiver)和具體命令實現(Concrete Command)的繼承, statementCallback
則是命令的抽象介面。
三、命令模式實戰
模擬在餐廳中點餐交給初始烹飪的場景,在該場景中點餐人員只需要把需要點的各種菜系交給服務員,服務員再把各項菜品交給廚師進行烹飪。如下圖所示:
我們先分析一下,命令是菜品具體實現是菜系,命令實現是廚師,呼叫者是服務員。所以該場景下的命令模式結構應該為:
程式碼目錄結構為:
├─src
│ ├─main
│ │ ├─java
│ │ │ └─cn
│ │ │ └─ethan
│ │ │ └─design
│ │ │ └─command
│ │ │ │ Waiter.java
│ │ │ │
│ │ │ ├─cook
│ │ │ │ │ ICook.java
│ │ │ │ │
│ │ │ │ └─impl
│ │ │ │ GuangDongCook.java
│ │ │ │ JiangSuCook.java
│ │ │ │ ShanDongCook.java
│ │ │ │ SiChuangCook.java
│ │ │ │
│ │ │ └─cuisine
│ │ │ │ ICuisine.java
│ │ │ │
│ │ │ └─impl
│ │ │ GuangDongCuisine.java
│ │ │ JiangSuCuisine.java
│ │ │ ShanDongCuisine.java
│ │ │ SiChuangCuisine.java
│ │ │
│ │ └─resources
│ └─test
│ └─java
│ └─cn
│ └─ethan
│ └─disign
│ ApiTest.java
具體程式碼如下:
- 抽象命令者及其具體實現
/**
* @description: 抽象命令介面(八大菜系)
* @author: wjw
* @date: 2022/4/5
*/
public interface ICuisine {
/**烹調公共介面*/
void cook();
}
/**
* @description: 具體命令實現(廣東菜)
* @author: wjw
* @date: 2022/4/5
*/
public class GuangDongCuisine implements ICuisine {
private ICook cook;
public GuangDongCuisine(ICook cook) {
this.cook = cook;
}
@Override
public void cook() {
cook.doCooking();
}
}
/**
* @description: 命令具體實現(江蘇菜)
* @author: wjw
* @date: 2022/4/5
*/
public class JiangSuCuisine implements ICuisine {
private ICook cook;
public JiangSuCuisine(ICook cook) {
this.cook = cook;
}
@Override
public void cook() {
cook.doCooking();
}
}
/**
* @description: 具體命令實現(山東菜)
* @author: wjw
* @date: 2022/4/5
*/
public class ShanDongCuisine implements ICuisine {
private ICook cook;
public ShanDongCuisine(ICook cook) {
this.cook = cook;
}
@Override
public void cook() {
cook.doCooking();
}
}
/**
* @description: 具體命令實現(四川菜)
* @author: wjw
* @date: 2022/4/5
*/
public class SiChuangCuisine implements ICuisine {
private ICook cook;
public SiChuangCuisine(ICook cook) {
this.cook = cook;
}
@Override
public void cook() {
cook.doCooking();
}
}
- 抽象實現者及其具體實現
/**
* @description: 抽象實現者介面
* @author: wjw
* @date: 2022/4/5
*/
public interface ICook {
/**廚師烹調*/
void doCooking();
}
/**
* @description: 具體實現者(廣東廚師)
* @author: wjw
* @date: 2022/4/5
*/
public class GuangDongCook implements ICook {
private Logger logger = LoggerFactory.getLogger(GuangDongCook.class);
@Override
public void doCooking() {
logger.info("廣東廚師,會做廣東菜");
}
}
/**
* @description: 具體實現類(江蘇廚師)
* @author: wjw
* @date: 2022/4/5
*/
public class JiangSuCook implements ICook {
private Logger logger = LoggerFactory.getLogger(JiangSuCook.class);
@Override
public void doCooking() {
logger.info("江蘇廚師,會燒江蘇菜");
}
}
/**
* @description: 具體實現類(山東廚師)
* @author: wjw
* @date: 2022/4/5
*/
public class ShanDongCook implements ICook {
private Logger logger = LoggerFactory.getLogger(ShanDongCook.class);
@Override
public void doCooking() {
logger.info("山東廚師會燒山東菜");
}
}
/**
* @description: 具體實現類(四川廚師)
* @author: wjw
* @date: 2022/4/5
*/
public class SiChuangCook implements ICook {
private Logger logger = LoggerFactory.getLogger(SiChuangCook.class);
@Override
public void doCooking() {
logger.info("四川廚師會燒四川菜");
}
}
- 呼叫者及客戶端
/**
* @description: 呼叫者(服務員)
* @author: wjw
* @date: 2022/4/5
*/
public class Waiter {
private Logger logger = LoggerFactory.getLogger(Waiter.class);
private List<ICuisine> cuisineList = new ArrayList<>();
public void order(ICuisine cuisine) {
cuisineList.add(cuisine);
}
public synchronized void placeOrder() {
for (ICuisine cuisine : cuisineList) {
cuisine.cook();
}
cuisineList.clear();
}
}
/**
* @description: 客戶端
* @author: wjw
* @date: 2022/4/5
*/
public class ApiTest {
@Test
public void test_command() {
//菜和廚師命令實現
ICuisine guangDongCuisine = new GuangDongCuisine(new GuangDongCook());
ICuisine shanDongCuisine = new ShanDongCuisine(new ShanDongCook());
ICuisine siChuangCuisine = new SiChuangCuisine(new SiChuangCook());
ICuisine jiangSuCuisine = new JiangSuCuisine(new JiangSuCook());
//呼叫者進行點單
Waiter waiter = new Waiter();
waiter.order(guangDongCuisine);
waiter.order(shanDongCuisine);
waiter.order(siChuangCuisine);
waiter.order(jiangSuCuisine);
//下單操作
waiter.placeOrder();
}
}
最終測試結果如下:
23:16:40.512 [main] INFO c.e.d.c.cook.impl.GuangDongCook - 廣東廚師,會做廣東菜
23:16:40.518 [main] INFO c.e.d.command.cook.impl.ShanDongCook - 山東廚師會燒山東菜
23:16:40.518 [main] INFO c.e.d.command.cook.impl.SiChuangCook - 四川廚師會燒四川菜
23:16:40.518 [main] INFO c.e.d.command.cook.impl.JiangSuCook - 江蘇廚師,會燒江蘇菜
參考資料
《重學Java設計模式》