設計模式系列—命令模式

設計模式系列—命令模式

作者公眾號:一角錢技術(org_yijiaoqian)

前言

23種設計模式速記

單例(singleton)模式

工廠方法(factory method)模式

抽象工廠(abstract factory)模式

建造者/構建器(builder)模式

原型(prototype)模式

享元(flyweight)模式

外觀(facade)模式

介面卡(adapter)模式

裝飾(decorator)模式

觀察者(observer)模式

策略(strategy)模式

橋接(bridge)模式

模版方法(template method)模式

責任鏈(chain of responsibility)模式

組合(composite)模式

代理(proxy)模式

備忘錄(memento)模式

持續更新中

……

23種設計模式快速記憶的請看上面第一篇,本篇和大家一起來學習

命令模式

相關內容。

設計模式系列—命令模式

模式定義

將一個請求封裝為一個物件,使發出請求的責任和執行請求的責任分割開。這樣兩者之間透過命令物件進行溝通,這樣方便將命令物件進行儲存、傳遞、呼叫、增加與管理。

在軟體開發系統中,常常出現“方法的請求者”與“方法的實現者”之間存在緊密的耦合關係。這不利於軟體功能的擴充套件與維護。例如,想對行為進行“撤銷、重做、記錄”等處理都很不方便,因此“如何將方法的請求者與方法的實現者解耦?”變得很重要,命令模式能很好地解決這個問題。

設計模式系列—命令模式

模版實現如下

package com。niuh。designpattern。command。v1;/** * 

 * 命令模式 * 

 */public class CommandPattern {    public static void main(String[] args) {        Command cmd = new ConcreteCommand();        Invoker ir = new Invoker(cmd);        System。out。println(“客戶訪問呼叫者的call()方法。。。”);        ir。call();    }}//抽象命令interface Command {    public abstract void execute();}//具體命令class ConcreteCommand implements Command {    private Receiver receiver;    ConcreteCommand() {        receiver = new Receiver();    }    public void execute() {        receiver。action();    }}//接收者class Receiver {    public void action() {        System。out。println(“接收者的action()方法被呼叫。。。”);    }}//呼叫者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();    }}

輸出結果如下

客戶訪問呼叫者的call()方法。。。呼叫者執行命令command。。。接收者的action()方法被呼叫。。。

解決的問題

在軟體系統中,行為請求者與行為實現者通常是一種緊耦合的關係,但某些場合,比如需要對行為進行記錄、撤銷或重做、事務等處理時,這種無法抵禦變化的緊耦合的設計就不太合適。

模式組成

可以將系統中的相關操作抽象成命令,使呼叫者與實現者相關分離,其結構如下。

設計模式系列—命令模式

例項說明

例項概況

結合命令模式,實現一個課程影片的開啟和關閉。

設計模式系列—命令模式

使用步驟

步驟1

:宣告執行命令的介面,擁有執行命令的抽象方法 execute()

interface Command {    void execute();}

步驟2

:定義具體命令角色,建立開啟課程連結 和 關閉課程連線

/** * 開啟課程連結 */class OpenCourseVideoCommand implements Command {    private CourseVideo courseVideo;    public OpenCourseVideoCommand(CourseVideo courseVideo) {        this。courseVideo = courseVideo;    }    @Override    public void execute() {        courseVideo。open();    }}/** * 關閉課程連結 */class CloseCourseVideoCommand implements Command {    private CourseVideo courseVideo;    public CloseCourseVideoCommand(CourseVideo courseVideo) {        this。courseVideo = courseVideo;    }    @Override    public void execute() {        courseVideo。close();    }}

步驟3

:定義接收者角色,執行命令功能的相關操作,是具體命令物件業務的真正實現者

class CourseVideo {    private String name;    public CourseVideo(String name) {        this。name = name;    }    public void open() {        System。out。println(this。name + “課程影片開放。”);    }    public void close() {        System。out。println(this。name + “課程影片關閉。”);    }}

步驟4

:建立User物件為請求的傳送者,即請求者角色

class User {    private List commands = new ArrayList<>();    public void addCommand(Command command) {        commands。add(command);    }    public void executeCommands() {        commands。forEach(Command::execute);        commands。clear();    }}

步驟4

:測試執行

public class CommandPattern {    public static void main(String[] args) {        //命令接收者        CourseVideo courseVideo = new CourseVideo(“設計模式系列”);        //建立命令        OpenCourseVideoCommand openCourseVideoCommand = new OpenCourseVideoCommand(courseVideo);        CloseCourseVideoCommand closeCourseVideoCommand = new CloseCourseVideoCommand(courseVideo);        //建立執行人        User user = new User();        //新增命令        user。addCommand(openCourseVideoCommand);        user。addCommand(closeCourseVideoCommand);        //執行        user。executeCommands();    }}

輸出結果

設計模式系列課程影片開放。設計模式系列課程影片關閉。

優點

降低系統的耦合度。命令模式能將呼叫操作的物件與實現該操作的物件解耦。

增加或刪除命令非常方便。採用命令模式增加與刪除命令不會影響其他類,它滿足“開閉原則”,對擴充套件比較靈活。

可以實現宏命令。命令模式可以與組合模式結合,將多個命令裝配成一個組合命令,即宏命令。

方便實現 Undo 和 Redo 操作。命令模式可以與後面介紹的備忘錄模式結合,實現命令的撤銷與恢復。

缺點

可能產生大量具體命令類。因為計對每一個具體操作都需要設計一個具體命令類,這將增加系統的複雜性。

應用場景

命令執行過程較為複雜且可能存在變化,需要對執行命令動作本身進行額外操作,此時可以考慮使用命令模式

命令模式的擴充套件

在軟體開發中,有時將命令模式與組合模式聯合使用,這就構成了宏命令模式,也叫組合命令模式。宏命令包含了一組命令,它充當了具體命令與呼叫者的雙重角色,執行它時將遞迴呼叫它所包含的所有命令,其具體結構圖如下:

設計模式系列—命令模式

模版實現如下

package com。niuh。designpattern。command。v2;import java。util。ArrayList;/** * 

 * 組合命令模式 * 

 */public class CompositeCommandPattern {    public static void main(String[] args) {        AbstractCommand cmd1 = new ConcreteCommand1();        AbstractCommand cmd2 = new ConcreteCommand2();        CompositeInvoker ir = new CompositeInvoker();        ir。add(cmd1);        ir。add(cmd2);        System。out。println(“客戶訪問呼叫者的execute()方法。。。”);        ir。execute();    }}//抽象命令interface AbstractCommand {    public abstract void execute();}//樹葉構件: 具體命令1class ConcreteCommand1 implements AbstractCommand {    private CompositeReceiver receiver;    ConcreteCommand1() {        receiver = new CompositeReceiver();    }    public void execute() {        receiver。action1();    }}//樹葉構件: 具體命令2class ConcreteCommand2 implements AbstractCommand {    private CompositeReceiver receiver;    ConcreteCommand2() {        receiver = new CompositeReceiver();    }    public void execute() {        receiver。action2();    }}//樹枝構件: 呼叫者class CompositeInvoker implements AbstractCommand {    private ArrayList children = new ArrayList();    public void add(AbstractCommand c) {        children。add(c);    }    public void remove(AbstractCommand c) {        children。remove(c);    }    public AbstractCommand getChild(int i) {        return children。get(i);    }    public void execute() {        for (Object obj : children) {            ((AbstractCommand) obj)。execute();        }    }}//接收者class CompositeReceiver {    public void action1() {        System。out。println(“接收者的action1()方法被呼叫。。。”);    }    public void action2() {        System。out。println(“接收者的action2()方法被呼叫。。。”);    }}

輸出結果如下

客戶訪問呼叫者的execute()方法。。。接收者的action1()方法被呼叫。。。接收者的action2()方法被呼叫。。。

命令模式還可以同備忘錄(Memento)模式組合使用,這樣就變成了可撤銷的命令模式

原始碼中的應用

java。util。Timer類中scheduleXXX()方法

java Concurrency Executor execute() 方法

java。lang。reflect。Method invoke()方法

org。springframework。jdbc。core。JdbcTemplate

……

在 JdbcTemplate 中的應用

在JdbcTemplate中命令模式的使用並沒有遵從標準的命令模式的使用,只是思想相同而已。

在 Spring 的 JdbcTemplate 這個類中有 query() 方法,query() 方法中定義了一個內部類 QueryStatementCallback,QueryStatementCallback 又實現了 StatementCallback 介面,另外還有其它類實現了該介面,StatementCallback 介面中又有一個抽象方法 doInStatement()。在 execute() 中又呼叫了 query()。

設計模式系列—命令模式

StatementCallback

充當的是命令角色,

JdbcTemplate

即充當呼叫者角色,又充當接收者角色。上面的類圖只是為了方便理解,實際上,QueryStatementCallback 與 ExecuteStatementCallback是JdbcTemplate中方法的內部類,具體看原始碼中的內容。

部分原始碼分析

StatementCallback介面:

public interface StatementCallback { T doInStatement(Statement stmt) throws SQLException, DataAccessException;}

JdbcTemplate類:

public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { //相當於呼叫者釋出的一個命令 @Override public  List query(String sql, RowMapper rowMapper) throws DataAccessException {  return query(sql, new RowMapperResultSetExtractor(rowMapper)); } //命令釋出後由具體的命令派給接收者進行執行 @Override public  T query(final String sql, final ResultSetExtractor rse) throws DataAccessException {  Assert。notNull(sql, “SQL must not be null”);  Assert。notNull(rse, “ResultSetExtractor must not be null”);  if (logger。isDebugEnabled()) {   logger。debug(“Executing SQL query [” + sql + “]”);  }  //內部類,實現StatementCallback,相當於具體的命令  class QueryStatementCallback implements StatementCallback, SqlProvider {   @Override   public T doInStatement(Statement stmt) throws SQLException {    ResultSet rs = null;    try {     rs = stmt。executeQuery(sql);     ResultSet rsToUse = rs;     if (nativeJdbcExtractor != null) {      rsToUse = nativeJdbcExtractor。getNativeResultSet(rs);     }     return rse。extractData(rsToUse);    }    finally {     JdbcUtils。closeResultSet(rs);    }   }   @Override   public String getSql() {    return sql;   }  }  return execute(new QueryStatementCallback()); } //相當於接收者,命令真正的執行者 @Override public  T execute(StatementCallback action) throws DataAccessException {  Assert。notNull(action, “Callback object must not be null”);  Connection con = DataSourceUtils。getConnection(getDataSource());  Statement stmt = null;  try {   Connection conToUse = con;   if (this。nativeJdbcExtractor != null &&     this。nativeJdbcExtractor。isNativeConnectionNecessaryForNativeStatements()) {    conToUse = this。nativeJdbcExtractor。getNativeConnection(con);   }   stmt = conToUse。createStatement();   applyStatementSettings(stmt);   Statement stmtToUse = stmt;   if (this。nativeJdbcExtractor != null) {    stmtToUse = this。nativeJdbcExtractor。getNativeStatement(stmt);   }   T result = action。doInStatement(stmtToUse);   handleWarnings(stmt);   return result;  }  catch (SQLException ex) {   // Release Connection early, to avoid potential connection pool deadlock   // in the case when the exception translator hasn‘t been initialized yet。   JdbcUtils。closeStatement(stmt);   stmt = null;   DataSourceUtils。releaseConnection(con, getDataSource());   con = null;   throw getExceptionTranslator()。translate(“StatementCallback”, getSql(action), ex);  }  finally {   JdbcUtils。closeStatement(stmt);   DataSourceUtils。releaseConnection(con, getDataSource());  } }}

PS

:以上程式碼提交在

Github

https://github。com/Niuh-Study/niuh-designpatterns。git

文章持續更新,可以公眾號搜一搜「

一角錢技術

」第一時間閱讀, 本文 GitHub

org_hejianhui/JavaStudy

已經收錄,歡迎 Star。