设计模式学习专栏五--------命令模式

设计模式学习专栏五--------命令模式

场景


设计一个智能遥控器,遥控器上有7个插槽 , 每个插槽(某设备具体供应商)对应两个按钮 on , off ,以及一个全局的撤销操作undo.

image

较差的实现方式

判断每个插槽对应的具体厂商是谁, 然后做出对应的动作

if(slot1 == Light){
    light.on()
}else if(slot1 == Hottub){
    hottub.on()
}else if(slot1 == TV){
    tv.on();
}
...
  • 出现的问题
    • 遥控器和具体的设备厂商耦合到了一起 , 遥控器需要 认识 某个插槽当前对应设备的厂商
    • 当加入/删除 新的设备厂商时, 当前代码都需要进行改动

如何解决


对象村餐厅的例子

image
  • 一张订单封装了 准备餐点的请求
  • 女招待的工作是接收订单 , 然后调用订单的OrderUp()方法 , 女招待不需要担心订单的内容是什么, 或者由谁来准备餐点 , 她只需要指导,订单由一个OrderUp()方法可以调用即可.
  • 快餐厨师具有准备餐点的知识 . 他只要看到订单就知道如何准备餐点 . 厨师和女招侍之间从不需要直接沟通.

把餐厅想象成设计模式的一种模型 , 这个模型允许通过"封装请求的命令"(订单)将 "发出请求的对象"(女招待, "订单来啦")"接收与执行这些请求的对象"(厨师) 分隔开 .

从餐厅到命令模式

image

命令模式总览


定义: 将请求封装成对象(订单),将发出请求的对象(服务员/按钮)和执行请求的对象(厨师/具体供应商)解耦 , 也可以支持撤销操作

  • 类图
image
  • 模式的理解

    • 角色

      • 封装请求的命令对象(订单) : 一个命令对象通过在特定接收者上绑定一组动作来封装一个请求 receiver.action1(), 该对象之暴露一个execute()方法, 当此方法被调用时, 接收者就会进行对应的动作 . 从女招待的角度来看, 它不需要知道具体哪个 接收者进行了什么动作,只知道如果调用execute()方法 , 请求的目的就能达到了
      • 发出请求的对象(女招待) : ①负责接收命令对象 ②在合适的时候调用命令对象的execute()方法
      • 执行请求的对象(厨师) : 真正执行请求的接收者
    • 细节

      • "撤销操作" 只需在Invocker中定义变量Command undoCommand保存撤销操作即可 . 如果要联系撤销, 则可以用的形式存储执行过的命令

        public class LightOnCommand implements Command {
          Light light;
        
          public LightOnCommand(Light light) {
              this.light = light;
          }
        
          public void execute() {
              light.on();
          }
        
          public void undo() {
              light.off();
          }
        }
        
        public class RemoteControlWithUndo {
          Command[] onCommands;
          Command[] offCommands;
          Command undoCommand;    //记录上一次命令
         
          public RemoteControlWithUndo() {
              onCommands = new Command[7];
              offCommands = new Command[7];
         
              Command noCommand = new NoCommand();
              for(int i=0;i<7;i++) {
                  onCommands[i] = noCommand;
                  offCommands[i] = noCommand;
              }
              undoCommand = noCommand;
          }
          
          public void setCommand(int slot, Command onCommand, Command offCommand) {
              onCommands[slot] = onCommand;
              offCommands[slot] = offCommand;
          }
         
          public void onButtonWasPushed(int slot) {
              onCommands[slot].execute();
              undoCommand = onCommands[slot]; //记录上一次命令
          }
         
          public void offButtonWasPushed(int slot) {
              offCommands[slot].execute();
              undoCommand = offCommands[slot];    //记录上一次命令
          }
         
          public void undoButtonWasPushed() {
              undoCommand.undo(); //执行撤销命令
          }
        }
        
- "Party模式" :  封装一次请求中的批量操作 (按下一个按钮,同时弄暗灯光 , 打开音响和电视,设置好DVD)

  ```java
  public class MacroCommand implements Command {
    Command[] commands;
   
    public MacroCommand(Command[] commands) {
        this.commands = commands;
    }
   
    public void execute() {
        for (int i = 0; i < commands.length; i++) {
            commands[i].execute();
        }
    }
  }
  ```
  • 使用场景

    • 队列请求 (日志安排 , 线程池 , 工作队列)
      • 以线程池为例 , 将线程需要执行的动作封装进Runnable对象中
      • 线程池 以 阻塞队列存储Runnable对象
      • 当线程空闲时 , 从队列中取出Runnable对象 , 并执行Runnable中重写的Run()方法
      • 总结 : 即线程池中的线程不需要知道具体的执行者是谁,干了什么,只需要在空闲时从队列中取出命令对象Runnable并调用run()方法即可
    • 日志请求
      • 当每个命令被执行时, 会被储存在磁盘中
      • 在系统死机后, 重新加载存储的命令,并以准备的次序执行

案例代码部分

image
  • 命令对象Command

    public interface Command {
      public void execute();
      public void undo();
    }
    
  • 具体命令对象--开灯

    public class LightOnCommand implements Command {
      Light light;
      int level;
      public LightOnCommand(Light light) {
          this.light = light;
      }
     
      public void execute() {
            level = light.getLevel();
          light.on();
      }
     
      public void undo() {
          light.dim(level);
      }
    }
    
  • 具体命令对象 --关灯

    public class LightOffCommand implements Command {
      Light light;
      int level;
      public LightOffCommand(Light light) {
          this.light = light;
      }
     
      public void execute() {
            level = light.getLevel();
          light.off();
      }
     
      public void undo() {
          light.dim(level);
      }
    }
    
  • 设备厂商 -- Receiver

    public class Light {
      String location;
      int level;
    
      public Light(String location) {
          this.location = location;
      }
    
      public void on() {
          level = 100;
          System.out.println("Light is on");
      }
    
      public void off() {
          level = 0;
          System.out.println("Light is off");
      }
    
      public void dim(int level) {
          this.level = level;
          if (level == 0) {
              off();
          }
          else {
              System.out.println("Light is dimmed to " + level + "%");
          }
      }
    
      public int getLevel() {
          return level;
      }
    }
    
  • 调用者Invoker --远程遥控器

    public class RemoteControlWithUndo {
      Command[] onCommands;
      Command[] offCommands;
      Command undoCommand;
     
      public RemoteControlWithUndo() {
          onCommands = new Command[7];
          offCommands = new Command[7];
     
          Command noCommand = new NoCommand();
          for(int i=0;i<7;i++) {
              onCommands[i] = noCommand;
              offCommands[i] = noCommand;
          }
          undoCommand = noCommand;
      }
      
      public void setCommand(int slot, Command onCommand, Command offCommand) {
          onCommands[slot] = onCommand;
          offCommands[slot] = offCommand;
      }
     
      public void onButtonWasPushed(int slot) {
          onCommands[slot].execute();
          undoCommand = onCommands[slot];
      }
     
      public void offButtonWasPushed(int slot) {
          offCommands[slot].execute();
          undoCommand = offCommands[slot];
      }
     
      public void undoButtonWasPushed() {
          undoCommand.undo();
      }
    }
    
  • 主程序

    public class RemoteLoader {
    
      public static void main(String[] args) {
          RemoteControlWithUndo remoteControl = new RemoteControlWithUndo();
    
          Light livingRoomLight = new Light("Living Room");
    
          LightOnCommand livingRoomLightOn =
                  new LightOnCommand(livingRoomLight);
          LightOffCommand livingRoomLightOff =
                  new LightOffCommand(livingRoomLight);
    
          remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
    
          remoteControl.onButtonWasPushed(0);
          remoteControl.offButtonWasPushed(0);
          System.out.println(remoteControl);
          remoteControl.undoButtonWasPushed();    //撤销操作
          System.out.println(remoteControl);      //此时应该是开灯状态
      }
    }
    
  • 输出结果

    Light is on
    Light is off
    
    ------ Remote Control -------
    [slot 0] headfirst.designpatterns.command.undo.LightOnCommand    headfirst.designpatterns.command.undo.LightOffCommand
    [slot 1] headfirst.designpatterns.command.undo.NoCommand    headfirst.designpatterns.command.undo.NoCommand
    [slot 2] headfirst.designpatterns.command.undo.NoCommand    headfirst.designpatterns.command.undo.NoCommand
    [slot 3] headfirst.designpatterns.command.undo.NoCommand    headfirst.designpatterns.command.undo.NoCommand
    [slot 4] headfirst.designpatterns.command.undo.NoCommand    headfirst.designpatterns.command.undo.NoCommand
    [slot 5] headfirst.designpatterns.command.undo.NoCommand    headfirst.designpatterns.command.undo.NoCommand
    [slot 6] headfirst.designpatterns.command.undo.NoCommand    headfirst.designpatterns.command.undo.NoCommand
    [undo] headfirst.designpatterns.command.undo.LightOffCommand
    
    Light is dimmed to 100%       //撤销操作 ==> 开灯状态
    
    ------ Remote Control -------
    [slot 0] headfirst.designpatterns.command.undo.LightOnCommand    headfirst.designpatterns.command.undo.LightOffCommand
    [slot 1] headfirst.designpatterns.command.undo.NoCommand    headfirst.designpatterns.command.undo.NoCommand
    [slot 2] headfirst.designpatterns.command.undo.NoCommand    headfirst.designpatterns.command.undo.NoCommand
    [slot 3] headfirst.designpatterns.command.undo.NoCommand    headfirst.designpatterns.command.undo.NoCommand
    [slot 4] headfirst.designpatterns.command.undo.NoCommand    headfirst.designpatterns.command.undo.NoCommand
    [slot 5] headfirst.designpatterns.command.undo.NoCommand    headfirst.designpatterns.command.undo.NoCommand
    [slot 6] headfirst.designpatterns.command.undo.NoCommand    headfirst.designpatterns.command.undo.NoCommand
    [undo] headfirst.designpatterns.command.undo.LightOffCommand
    

参考

​ 书籍: HeadFirst设计模式

​ 代码参考地址: 我就是那个地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352