設計模式學習筆記(十五)命令模式及在Spring JdbcTemplate 中的實現

歸斯君發表於2022-04-05

命令(Command)模式是指將請求封裝成為一個物件,使發出請求和執行請求的責任分割開,方便將命令物件進行儲存、傳遞、呼叫、增加與管理。

也就是將傳送者、接收者和呼叫命令封裝成獨立的物件,來供客戶端呼叫。屬於行為模式的一種。

一、命令模式介紹

命令模式將傳送者與接受者完全解耦,傳送者與接收者之間沒有直接的聯絡,傳送者只需要如何傳送請求,而不需要關心請求是如何完成的。下面就來看看命令模式的結構和實現:

1.1 命令模式的結構

將呼叫者和實現者進行分離,其結構如下所示:

image-20220405200443779

  • 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()方法:

image-20220405165646955

我們看到,上面的query()方法中定義了一個內部類QueryStatementCallback,並實現了StatementCallback介面,點開檢視詳細內容:

@FunctionalInterface
public interface StatementCallback<T> {
    //唯一的抽象方法
    @Nullable
    T doInStatement(Statement var1) throws SQLException, DataAccessException;
}

回到query()方法中,我們發現最後返回的execute(new QueryStatementCallback())中是將內部類QueryStatementCallback當做引數進行返回。這裡QueryStatementCallback就相當於命令模式中的具體命令物件,而StatementCallback則是抽象命令物件。比如還有其他具體命令實現類,比如BatchUpdateStatementCallbackExecuteStatementCallback等等:

image-20220405174432889

看看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;
}

根據上面的程式碼,可以梳理整個執行流程:

image-20220405195756378

實際上JdbcTemplate這個類是呼叫者(Invoker)、實現者(Receiver)和具體命令實現(Concrete Command)的繼承, statementCallback則是命令的抽象介面。

三、命令模式實戰

模擬在餐廳中點餐交給初始烹飪的場景,在該場景中點餐人員只需要把需要點的各種菜系交給服務員,服務員再把各項菜品交給廚師進行烹飪。如下圖所示:

image-20220405205119777

我們先分析一下,命令是菜品具體實現是菜系,命令實現是廚師,呼叫者是服務員。所以該場景下的命令模式結構應該為:

image-20220405205915202

程式碼目錄結構為:

├─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

具體程式碼如下:

  1. 抽象命令者及其具體實現
/**
 * @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();
    }
}
  1. 抽象實現者及其具體實現
/**
 * @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("四川廚師會燒四川菜");
    }
}
  1. 呼叫者及客戶端
/**
 * @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設計模式》

http://c.biancheng.net/view/1380.html

相關文章