设计模式系列—命令模式

开发 前端
本篇和大家一起来学习命令模式相关内容。

模式定义

将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。

在软件开发系统中,常常出现“方法的请求者”与“方法的实现者”之间存在紧密的耦合关系。这不利于软件功能的扩展与维护。例如,想对行为进行“撤销、重做、记录”等处理都很不方便,因此“如何将方法的请求者与方法的实现者解耦?”变得很重要,命令模式能很好地解决这个问题。

模版实现如下:

  1. package com.niuh.designpattern.command.v1; 
  2.  
  3. /** 
  4.  * <p> 
  5.  * 命令模式 
  6.  * </p> 
  7.  */ 
  8. public class CommandPattern { 
  9.     public static void main(String[] args) { 
  10.         Command cmd = new ConcreteCommand(); 
  11.         Invoker ir = new Invoker(cmd); 
  12.         System.out.println("客户访问调用者的call()方法..."); 
  13.         ir.call(); 
  14.     } 
  15.  
  16. //抽象命令 
  17. interface Command { 
  18.     public abstract void execute(); 
  19.  
  20. //具体命令 
  21. class ConcreteCommand implements Command { 
  22.     private Receiver receiver; 
  23.  
  24.     ConcreteCommand() { 
  25.         receiver = new Receiver(); 
  26.     } 
  27.  
  28.     public void execute() { 
  29.         receiver.action(); 
  30.     } 
  31.  
  32. //接收者 
  33. class Receiver { 
  34.     public void action() { 
  35.         System.out.println("接收者的action()方法被调用..."); 
  36.     } 
  37.  
  38. //调用者 
  39. class Invoker { 
  40.     private Command command; 
  41.  
  42.     public Invoker(Command command) { 
  43.         this.command = command; 
  44.     } 
  45.  
  46.     public void setCommand(Command command) { 
  47.         this.command = command; 
  48.     } 
  49.  
  50.     public void call() { 
  51.         System.out.println("调用者执行命令command..."); 
  52.         command.execute(); 
  53.     } 

输出结果如下:

  1. 客户访问调用者的call()方法... 
  2. 调用者执行命令command... 
  3. 接收者的action()方法被调用... 

解决的问题

在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。

模式组成

 

可以将系统中的相关操作抽象成命令,使调用者与实现者相关分离,其结构如下。

实例说明

实例概况

 

结合命令模式,实现一个课程视频的打开和关闭。

使用步骤

 

步骤1:声明执行命令的接口,拥有执行命令的抽象方法 execute()

  1. interface Command { 
  2.     void execute(); 

步骤2:定义具体命令角色,创建打开课程链接 和 关闭课程连接

  1. /** 
  2.  * 打开课程链接 
  3.  */ 
  4. class OpenCourseVideoCommand implements Command { 
  5.  
  6.     private CourseVideo courseVideo; 
  7.  
  8.     public OpenCourseVideoCommand(CourseVideo courseVideo) { 
  9.         this.courseVideo = courseVideo; 
  10.     } 
  11.  
  12.     @Override 
  13.     public void execute() { 
  14.         courseVideo.open(); 
  15.     } 
  16.  
  17. /** 
  18.  * 关闭课程链接 
  19.  */ 
  20. class CloseCourseVideoCommand implements Command { 
  21.  
  22.     private CourseVideo courseVideo; 
  23.  
  24.     public CloseCourseVideoCommand(CourseVideo courseVideo) { 
  25.         this.courseVideo = courseVideo; 
  26.     } 
  27.  
  28.     @Override 
  29.     public void execute() { 
  30.         courseVideo.close(); 
  31.     } 

步骤3:定义接收者角色,执行命令功能的相关操作,是具体命令对象业务的真正实现者

  1. class CourseVideo { 
  2.  
  3.     private String name
  4.  
  5.     public CourseVideo(String name) { 
  6.         this.name = name
  7.     } 
  8.  
  9.     public void open() { 
  10.         System.out.println(this.name + "课程视频开放。"); 
  11.     } 
  12.  
  13.     public void close() { 
  14.         System.out.println(this.name + "课程视频关闭。"); 
  15.     } 

步骤4:创建User对象为请求的发送者,即请求者角色

  1. class User { 
  2.  
  3.     private List<Command> commands = new ArrayList<>(); 
  4.  
  5.     public void addCommand(Command command) { 
  6.         commands.add(command); 
  7.     } 
  8.  
  9.     public void executeCommands() { 
  10.         commands.forEach(Command::execute); 
  11.         commands.clear(); 
  12.     } 

步骤5:测试执行

  1. public class CommandPattern { 
  2.  
  3.     public static void main(String[] args) { 
  4.         //命令接收者 
  5.         CourseVideo courseVideo = new CourseVideo("设计模式系列"); 
  6.  
  7.         //创建命令 
  8.         OpenCourseVideoCommand openCourseVideoCommand = new OpenCourseVideoCommand(courseVideo); 
  9.         CloseCourseVideoCommand closeCourseVideoCommand = new CloseCourseVideoCommand(courseVideo); 
  10.  
  11.         //创建执行人 
  12.         User user = new User(); 
  13.  
  14.         //添加命令 
  15.         user.addCommand(openCourseVideoCommand); 
  16.         user.addCommand(closeCourseVideoCommand); 
  17.  
  18.         //执行 
  19.         user.executeCommands(); 
  20.     } 

输出结果

  • 设计模式系列课程视频开放。
  • 设计模式系列课程视频关闭。

优点

  1. 降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。
  2. 增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。
  3. 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
  4. 方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。

缺点

可能产生大量具体命令类。因为计对每一个具体操作都需要设计一个具体命令类,这将增加系统的复杂性。

应用场景

命令执行过程较为复杂且可能存在变化,需要对执行命令动作本身进行额外操作,此时可以考虑使用命令模式

命令模式的扩展

 

在软件开发中,有时将命令模式与组合模式联合使用,这就构成了宏命令模式,也叫组合命令模式。宏命令包含了一组命令,它充当了具体命令与调用者的双重角色,执行它时将递归调用它所包含的所有命令,其具体结构图如下:

模版实现如下:

  1. package com.niuh.designpattern.command.v2; 
  2.  
  3. import java.util.ArrayList; 
  4.  
  5. /** 
  6.  * <p> 
  7.  * 组合命令模式 
  8.  * </p> 
  9.  */ 
  10. public class CompositeCommandPattern { 
  11.     public static void main(String[] args) { 
  12.         AbstractCommand cmd1 = new ConcreteCommand1(); 
  13.         AbstractCommand cmd2 = new ConcreteCommand2(); 
  14.         CompositeInvoker ir = new CompositeInvoker(); 
  15.         ir.add(cmd1); 
  16.         ir.add(cmd2); 
  17.         System.out.println("客户访问调用者的execute()方法..."); 
  18.         ir.execute(); 
  19.     } 
  20.  
  21. //抽象命令 
  22. interface AbstractCommand { 
  23.     public abstract void execute(); 
  24.  
  25. //树叶构件: 具体命令1 
  26. class ConcreteCommand1 implements AbstractCommand { 
  27.     private CompositeReceiver receiver; 
  28.  
  29.     ConcreteCommand1() { 
  30.         receiver = new CompositeReceiver(); 
  31.     } 
  32.  
  33.     public void execute() { 
  34.         receiver.action1(); 
  35.     } 
  36.  
  37. //树叶构件: 具体命令2 
  38. class ConcreteCommand2 implements AbstractCommand { 
  39.     private CompositeReceiver receiver; 
  40.  
  41.     ConcreteCommand2() { 
  42.         receiver = new CompositeReceiver(); 
  43.     } 
  44.  
  45.     public void execute() { 
  46.         receiver.action2(); 
  47.     } 
  48.  
  49. //树枝构件: 调用者 
  50. class CompositeInvoker implements AbstractCommand { 
  51.     private ArrayList<AbstractCommand> children = new ArrayList<AbstractCommand>(); 
  52.  
  53.     public void add(AbstractCommand c) { 
  54.         children.add(c); 
  55.     } 
  56.  
  57.     public void remove(AbstractCommand c) { 
  58.         children.remove(c); 
  59.     } 
  60.  
  61.     public AbstractCommand getChild(int i) { 
  62.         return children.get(i); 
  63.     } 
  64.  
  65.     public void execute() { 
  66.         for (Object obj : children) { 
  67.             ((AbstractCommand) obj).execute(); 
  68.         } 
  69.     } 
  70.  
  71. //接收者 
  72. class CompositeReceiver { 
  73.     public void action1() { 
  74.         System.out.println("接收者的action1()方法被调用..."); 
  75.     } 
  76.  
  77.     public void action2() { 
  78.         System.out.println("接收者的action2()方法被调用..."); 
  79.     } 

输出结果如下:

  • 客户访问调用者的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接口:

  1. public interface StatementCallback<T> { 
  2.  T doInStatement(Statement stmt) throws SQLException, DataAccessException; 

JdbcTemplate类:

  1. public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { 
  2.  //相当于调用者发布的一个命令 
  3.  @Override 
  4.  public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException { 
  5.   return query(sql, new RowMapperResultSetExtractor<T>(rowMapper)); 
  6.  } 
  7.  //命令发布后由具体的命令派给接收者进行执行 
  8.  @Override 
  9.  public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException { 
  10.   Assert.notNull(sql, "SQL must not be null"); 
  11.   Assert.notNull(rse, "ResultSetExtractor must not be null"); 
  12.   if (logger.isDebugEnabled()) { 
  13.    logger.debug("Executing SQL query [" + sql + "]"); 
  14.   } 
  15.   //内部类,实现StatementCallback,相当于具体的命令 
  16.   class QueryStatementCallback implements StatementCallback<T>, SqlProvider { 
  17.    @Override 
  18.    public T doInStatement(Statement stmt) throws SQLException { 
  19.     ResultSet rs = null
  20.     try { 
  21.      rs = stmt.executeQuery(sql); 
  22.      ResultSet rsToUse = rs; 
  23.      if (nativeJdbcExtractor != null) { 
  24.       rsToUse = nativeJdbcExtractor.getNativeResultSet(rs); 
  25.      } 
  26.      return rse.extractData(rsToUse); 
  27.     } 
  28.     finally { 
  29.      JdbcUtils.closeResultSet(rs); 
  30.     } 
  31.    } 
  32.    @Override 
  33.    public String getSql() { 
  34.     return sql; 
  35.    } 
  36.   } 
  37.   return execute(new QueryStatementCallback()); 
  38.  } 
  39.  //相当于接收者,命令真正的执行者 
  40.  @Override 
  41.  public <T> T execute(StatementCallback<T> action) throws DataAccessException { 
  42.   Assert.notNull(action"Callback object must not be null"); 
  43.   Connection con = DataSourceUtils.getConnection(getDataSource()); 
  44.   Statement stmt = null
  45.   try { 
  46.    Connection conToUse = con; 
  47.    if (this.nativeJdbcExtractor != null && 
  48.      this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) { 
  49.     conToUse = this.nativeJdbcExtractor.getNativeConnection(con); 
  50.    } 
  51.    stmt = conToUse.createStatement(); 
  52.    applyStatementSettings(stmt); 
  53.    Statement stmtToUse = stmt; 
  54.    if (this.nativeJdbcExtractor != null) { 
  55.     stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt); 
  56.    } 
  57.    T result = action.doInStatement(stmtToUse); 
  58.    handleWarnings(stmt); 
  59.    return result; 
  60.   } 
  61.   catch (SQLException ex) { 
  62.    // Release Connection early, to avoid potential connection pool deadlock 
  63.    // in the case when the exception translator hasn't been initialized yet. 
  64.    JdbcUtils.closeStatement(stmt); 
  65.    stmt = null
  66.    DataSourceUtils.releaseConnection(con, getDataSource()); 
  67.    con = null
  68.    throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex); 
  69.   } 
  70.   finally { 
  71.    JdbcUtils.closeStatement(stmt); 
  72.    DataSourceUtils.releaseConnection(con, getDataSource()); 
  73.   } 
  74.  } 

PS:以上代码提交在 Github :

 

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

责任编辑:姜华 来源: 今日头条
相关推荐

2022-01-12 13:33:25

工厂模式设计

2020-10-23 09:40:26

设计模式

2020-11-04 08:54:54

状态模式

2023-05-04 08:47:31

命令模式抽象接口

2021-10-28 19:09:09

模式原型Java

2021-10-26 00:21:19

设计模式建造者

2012-01-13 15:59:07

2020-10-20 13:33:00

建造者模式

2020-10-28 11:56:47

桥接模式

2020-11-09 08:20:33

解释器模式

2020-11-05 09:38:07

中介者模式

2021-03-02 08:50:31

设计单例模式

2021-09-29 13:53:17

抽象工厂模式

2021-06-09 08:53:34

设计模式策略模式工厂模式

2020-10-19 09:28:00

抽象工厂模式

2013-11-26 15:48:53

Android设计模式SDK

2022-01-14 09:22:22

设计模式桥接

2020-10-21 14:29:15

原型模式

2012-07-10 02:01:53

设计模式命令模式

2020-11-02 10:41:33

备忘录模式
点赞
收藏

51CTO技术栈公众号