文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

设计模式系列—命令模式

2024-12-03 18:23

关注

模式定义

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

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

模版实现如下:

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

输出结果如下:

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

解决的问题

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

模式组成

 

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

实例说明

实例概况

 

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

使用步骤

 

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

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

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

  1.  
  2. class OpenCourseVideoCommand implements Command { 
  3.  
  4.     private CourseVideo courseVideo; 
  5.  
  6.     public OpenCourseVideoCommand(CourseVideo courseVideo) { 
  7.         this.courseVideo = courseVideo; 
  8.     } 
  9.  
  10.     @Override 
  11.     public void execute() { 
  12.         courseVideo.open(); 
  13.     } 
  14.  
  15.  
  16. class CloseCourseVideoCommand implements Command { 
  17.  
  18.     private CourseVideo courseVideo; 
  19.  
  20.     public CloseCourseVideoCommand(CourseVideo courseVideo) { 
  21.         this.courseVideo = courseVideo; 
  22.     } 
  23.  
  24.     @Override 
  25.     public void execute() { 
  26.         courseVideo.close(); 
  27.     } 

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

输出结果如下:

命令模式还可以同备忘录(Memento)模式组合使用,这样就变成了可撤销的命令模式

源码中的应用

在 JdbcTemplate 中的应用

在JdbcTemplate中命令模式的使用并没有遵从标准的命令模式的使用,只是思想相同而已。

 

在 Spring 的 JdbcTemplate 这个类中有 query() 方法,query() 方法中定义了一个内部类 QueryStatementCallback,QueryStatementCallback 又实现了 StatementCallback 接口,另外还有其它类实现了该接口,StatementCallback 接口中又有一个抽象方法 doInStatement()。在 execute() 中又调用了 query()。

StatementCallback充当的是命令角色,JdbcTemplate即充当调用者角色,又充当接收者角色。上面的类图只是为了方便理解,实际上,QueryStatementCallback 与 ExecuteStatementCallback是JdbcTemplate中方法的内部类,具体看源码中的内容。

部分源码分析

 

StatementCallback接口:

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

JdbcTemplate类:

  1. public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { 
  2.  //相当于调用者发布的一个命令 
  3.  @Override 
  4.  public  List query(String sql, RowMapper rowMapper) throws DataAccessException { 
  5.   return query(sql, new RowMapperResultSetExtractor(rowMapper)); 
  6.  } 
  7.  //命令发布后由具体的命令派给接收者进行执行 
  8.  @Override 
  9.  public  T query(final String sql, final ResultSetExtractor 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, 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 execute(StatementCallback 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

来源:今日头条内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯