• 14.java设计模式之命令模式


    基本需求

    • 一套智能家电,有照明灯、风扇、冰箱、洗衣机,我们只要在手机上安装app就可以控制对这些家电工作
    • 这些智能家电来自不同的厂家,我们不想针对每一种家电都安装一个App分别控制,我们希望只要一个app就可以控制全部智能家电
    • 要实现一个app控制所有智能家电的需要,则每个智能家电厂家都要提供一个统一的接口给app调用,这时就可以考虑使用命令模式
    • 命令模式可将“动作的请求者”从“动作的执行者”对象中解耦出来
    • 也就是说,每一种家电都有开和关两种操作,一组命令

    基本介绍

    • 命令模式(Command):在软件设计中,我们经常需要向某些对象发送请求,并不知道请求的接收者是谁,不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计

    • 命令模式使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦

    • 命令模式中,会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求(即命令),同时命令模式也必须支持可撤销的操作

    • 将军发布命令,士兵去执行。其中有几个角色:将军(命令发布者)、士兵(命令的具体执行者)、命令(连接将军和士兵)

      • Invoker 是调用者(将军),Receiver 是被调用者(士兵),MyCommand 是命令,实现了 Command 接口,持有接收对象
    • UML类图(原理)

      • 说明
        • Invoker:命令调用者角色,聚合命令
        • Command:是命令接口
        • ConcreteCommand:命令的具体实现,聚合命令的接受者
        • Receiver:命令的接受者(执行者)
    • UML类图(案例)

    • 代码实现

      • public class LightReceiver {
        
           // 电灯命令的执行者
        
           public void on() {
               System.out.println("电灯打开了");
           }
        
           public void off() {
               System.out.println("电灯关闭了");
           }
        
        }
        
      • public interface Command {
        
           // 命令接口
        
           // 执行命令和撤销命令
           void execute();
        
           void undo();
        
        }
        
        // 子类一 电灯开命令
        class LightOnCommand implements Command{
        
           // 聚合命令的执行者
           private LightReceiver lightReceiver;
        
           public LightOnCommand(LightReceiver lightReceiver) {
               this.lightReceiver = lightReceiver;
           }
        
           @Override
           public void execute() {
               lightReceiver.on();
           }
        
           @Override
           public void undo() {
               lightReceiver.off();
           }
        
        }
        
        // 子类二 电灯关命令
        class LightOffCommand implements Command{
        
           // 聚合命令的执行者
           private LightReceiver lightReceiver;
        
           public LightOffCommand(LightReceiver lightReceiver) {
               this.lightReceiver = lightReceiver;
           }
        
           @Override
           public void execute() {
               lightReceiver.off();
           }
        
           @Override
           public void undo() {
               lightReceiver.on();
           }
        
        }
        
        // 子类三 空命令 对命令接口空实现
        class NoCommand implements Command {
        
           @Override
           public void execute() {
        
           }
        
           @Override
           public void undo() {
        
           }
        
        }
        
      • // 命令调用者
        public class RemoteController {
        
           // 开命令的集合
           private Command[] onCommands;
        
           // 关命令的集合
           private Command[] offCommands;
        
           // 记录上一个执行的命令,便于进行撤销
           private Command undoCommand;
        
           public RemoteController() {
               // 默认初始化五组开关 一组开关有开和关两个按钮,一一对应
               this.onCommands = new Command[5];
               this.offCommands = new Command[5];
               for (int i = 0; i < 5; i++) {
                   // 将开关命令的值初始化成空命令,防止空指针异常
                   this.onCommands[i] = new NoCommand();
                   this.offCommands[i] = new NoCommand();
               }
               this.undoCommand = new NoCommand();
           }
        
           // 为某组开关设置命令
           public void setCommand(int index, Command onCommand, Command offCommand) {
               if (index >= 0 && index < this.onCommands.length) {
                   this.onCommands[index] = onCommand;
                   this.offCommands[index] = offCommand;
               }
           }
        
           // 按下开的按钮,发送命令执行
           public void onButtonWasPushed(int index) {
               if (index >= 0 && index < this.onCommands.length) {
                   this.onCommands[index].execute();
                   // 设置撤销命令
                   this.undoCommand = this.onCommands[index];
               }
           }
        
           // 按下关的按钮,发送命令执行
           public void offButtonWasPushed(int index) {
               if (index >= 0 && index < this.onCommands.length) {
                   this.offCommands[index].execute();
                   // 设置撤销命令
                   this.undoCommand = this.offCommands[index];
               }
           }
        
           // 按下撤销按钮,发送命令执行,只支持撤销一次
           public void undoButtonWasPushed() {
               this.undoCommand.undo();
               // 重置撤销命令
               this.undoCommand = new NoCommand();
           }
        
        }
        
        
      • public class Client {
           public static void main(String[] args) {
               // 创建执行者
               LightReceiver lightReceiver = new LightReceiver();
               // 创建一组电灯开关的命令,并设置执行者
               Command lightOnCommand = new LightOnCommand(lightReceiver);
               Command lightOffCommand = new LightOffCommand(lightReceiver);
               // 创建命令的发送者,并设置电灯这一组命令
               RemoteController remoteController = new RemoteController();
               remoteController.setCommand(0, lightOnCommand, lightOffCommand);
               // 发送电灯命令 执行
               System.out.println("------发送电灯开命令------");
               remoteController.onButtonWasPushed(0);
               System.out.println("------发送电灯关命令------");
               remoteController.offButtonWasPushed(0);
               System.out.println("------发送撤销命令------");
               remoteController.undoButtonWasPushed();
        
               // 使用命令模式对命令进行了封装,将命令的发布者和执行者进行了松耦合,利于系统的扩展
               // 比如再有一组电视的命令,直接创建新的命令类,创建其对象将其设置给命令的发布者即可,原有的代码不需要改变
           }
        }
        

    spring源码

    • 在spring的JdbcTemplate类中就使用到了命令模式

    • // 在JdbcTemplate的query和execute方法中有如下代码
      public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {
         ......
         // StatementCallback是一个接口,只有一个doInStatement方法,相当于命令接口
         // QueryStatementCallback局部内部类 实现了StatementCallback,相当于具体的命令的,另外,在此处还充当了命令的执行者
         // StatementCallback接口共有四个实现类,均在JdbcTemplate某个方法内部,作为局部内部类
         // BatchUpdateStatementCallback、UpdateStatementCallback、QueryStatementCallback、ExecuteStatementCallback
         class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
             ......
             public T doInStatement(Statement stmt) throws SQLException {
                 
             }
             ......
         }
         // 调用execute方法
         return this.execute((StatementCallback)(new QueryStatementCallback()));   
      }
      
      // 在execute方法内部调用了命令接口中的方法,而execute方法又属于JdbcTemplate,即JdbcTemplate为命令的调用者
      public <T> T execute(StatementCallback<T> action) throws DataAccessException {
         ......
         T result = action.doInStatement(stmt);
         ......
      }
      

    注意事项

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

    • 命令模式是一种数据驱动的设计模式,属于行为型模式,请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令

    • 将发起请求的对象与执行请求的对象解耦。发起请求的对象是调用者,调用者只要调用命令对象的 execute()方法就可以让接收者工作,而不必知道具体的接收者对象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说:”请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到了纽带桥梁的作用

    • 容易设计一个命令队列,只要把命令对象放到列队,就可以多线程的执行命令

    • 容易实现对请求的撤销和重做

    • 命令模式不足:可能导致某些系统有过多的具体命令类,增加了系统的复杂度,这点在在使用的时候要注意

    • 空命令也是一种设计模式,它为我们省去了判空的操作,在上面的实例中,如果没有用空命令,我们每按下一个按键都要判空,这给我们编码带来一定的麻烦

    • 命令模式经典的应用场景:界面的一个按钮都是一条命令、模拟 CMD(DOS 命令)订单的撤销/恢复、触发-反馈机制

  • 相关阅读:
    第十六天-面向对象02-成员
    第十五天-面向对象01
    第十四天-内置函数
    第十三天-生成器
    第十二天-函数名 迭代器
    第十一天-函数进阶
    第十天-初识函数
    第九天- 文件操作 r w a 文件复制/修改
    objectives-c基本语法。
    今天开始了objective-c的学习!
  • 原文地址:https://www.cnblogs.com/xiaokantianse/p/14036353.html
Copyright © 2020-2023  润新知