【Head First 设计模式】命令模式

一、背景

本内容是来自书籍《Head First 设计模式》的第五章,命令模式(将方法调用封装起来)

二、OO基础

  1. 封装
  2. 继承
  3. 多态
  4. 抽象

三、OO设计原则

  1. 封装变化,将变化的部分与不变的部分分开
  2. 面向接口编程,而非面向实现编程
  3. 组合优于继承
  4. 对扩展开放,对修改关闭
  5. 为交互对象之间的松耦合而努力
  6. 高组件不应该依赖低组件,他们都应该依赖其抽象

四、认识命令模式

命令模式——将请求封装成对象,这可以让你使用不同的请求、队列,或者日志请求来参数化其他对象。命令模式也可以支持撤销操作

0.命令模式解决了什么?

  以一个家庭遥控器举例:现在有一个遥控器,它控制着房间里不同电器的开关,例如吊灯、卧室灯、空调、电视等,它们之间的场景有可能不同,所以提供的接口就有可能不同。
  例如吊灯提供了一个lightOn()的开接口与一个lightOff()的关接口,卧室灯却提供了一个on()的开接口与一个off()的关接口,诸如此场景的其他电器很多。那么遥控器怎么设计才能提供控制不同电器开关的功能呢?
  首先,给每个电器提供一个接口来控制开关是不可行的,因为此种方案会对系统的可维护性和可扩展性大打折扣。所以前辈们总结出了命令模式,它封装了内部复杂多样的接口,对外部提供一个统一的接口来进行调用。也就是说,客户在使用遥控器的时候不关心它内部是怎么执行的,客户只需要知道我按某一个按钮对应这一种电器的开或关即可。

1.遥控器的设计(开&关&撤销按钮)

下面我们就来看下遥控器是如何通过使用命令模式来实现的。

a.设计

在学习之前,我们先来看下遥控器的执行流程:
在这里插入图片描述
将上面的演示图转换成类图来看就是这样的:
在这里插入图片描述
  client就相当于用户,client–>invoker相当于用户创建了一批请求对象并设置进invoker然后调用invoker里的某一个接口方法来实现某种功能。而这个接口方法内部就是调用某个Command的execute()方法也就相当于遥控器执行内部逻辑,最后具体的CommandObject再调用内部receiver的一系列action动作来完成功能。

b.实现

最后,我们来看下代码实现:
(1)、Receiver

public interface Light {
  public void on();
  public void off();
}
public class LivingRoomLight implements Light{
  @Override
  public void on() {
    System.out.println("Living Room Light is On!");
  }

  @Override
  public void off() {
    System.out.println("Living Room Light is Off!");
  }
}

(2)、CommandObject

public interface Command {
  public void execute();
  public void undo();
}
public class NoCommand implements Command{
  @Override
  public void execute() {

  }

  @Override
  public void undo() {

  }
}
public class LightOnCommand implements Command {
  private Light light;

  public LightOnCommand(Light light) {
    this.light = light;
  }

  @Override
  public void execute() {
    light.on();
  }

  @Override
  public void undo() {
    light.off();
  }
}
public class LightOffCommand implements Command{
  private Light light;

  public LightOffCommand(Light light) {
    this.light = light;
  }

  @Override
  public void execute() {
    light.off();
  }

  @Override
  public void undo() {
    light.on();
  }
}

(3)、Invoker

public class RemoteControl {
  private Command[] onCommands;
  private Command[] offCommands;
  private Stack<Command> historyCommands;

  public RemoteControl() {
    onCommands = new Command[7];
    offCommands = new Command[7];

    for (int i = 0; i < 7; i++) {
      onCommands[i] = new NoCommand();
      offCommands[i] = new NoCommand();
    }
    historyCommands = new Stack<>();
  }

  public void setCommand(int slot, Command onCommand, Command offCommand) {
    onCommands[slot] = onCommand;
    offCommands[slot] = offCommand;
  }

  public void onButtonWasPressed(int slot) {
    onCommands[slot].execute();
    historyCommands.push(onCommands[slot]);
  }

  public void offButtonWasPressed(int slot) {
    offCommands[slot].execute();
    historyCommands.push(offCommands[slot]);
  }

  public void undo() {
    if (!historyCommands.isEmpty()){
      Command command = historyCommands.pop();
      if (null != command) {
        command.undo();
      }
    }else{
      System.out.println("无历史操作!");
    }
  }

  public String toString() {
    StringBuffer stringBuffer = new StringBuffer();
    stringBuffer.append("\n------Remote Control------\n");
    for (int i = 0; i < onCommands.length; i++) {
      stringBuffer.append("[slot " + i + "]" + onCommands[i].getClass().getName() + "    " + offCommands[i].getClass().getName() + "\n");
    }
    return stringBuffer.toString();
  }
}

(4)、Client

上面的command对象只实现了灯的开关指令,其他的电器同理,实现Command接口的execute、undo方法,调用其具体的receiver的action即可

public class ClientDemo {
  public static void main(String[] args) {
    RemoteControl remoteControl = new RemoteControl();

    Light light = new LivingRoomLight();
    LightOnCommand lightOnCommand = new LightOnCommand(light);
    LightOffCommand lightOffCommand = new LightOffCommand(light);

    remoteControl.setCommand(0,lightOnCommand,lightOffCommand);
    remoteControl.onButtonWasPressed(0);//开
    remoteControl.undo();//关
    remoteControl.offButtonWasPressed(0);//关闭
    remoteControl.onButtonWasPressed(0);//开
    remoteControl.undo();//关
    remoteControl.undo();//开
    remoteControl.undo();//无操作

  }
}

测试结果图:
在这里插入图片描述

c.实现过程中的总结

  可以看到上面有个NoCommand对象,它提供的空实现。在我们日常开发的时候也需要这样,当我们不想返回一个有意义的对象时,空对象就很有用。使用者可以将处理null的责任转移给空对象。
  在看完上面简单实现之后,我们就可以通过设计Command对象来支持不同的功能。例如吊扇基于状态的不同去创建不同的命令对象来执行不同的命令,再例如宏命令(一堆命令的集合),创建多个命令,将这些命令放到宏命令对象中,然后将宏命令对象设置进Invoker中,当我们调用Invoker里面的这个方法时就可以执行多个命令了。

d.命令模式的拓展

  命令可以将运算块打包(一个接收者和一组动作),然后将它传来传去,就像一般的对象一样。现在,即使在命令对象被创建许久之后,运算依然可以被调用。事实上,它甚至可以在不同的线程中被调用。我们可以利用这样的特性衍生一些应用,例如:日程安排(Scheduler)、线程池、工作队列等。
  再者,像某些应用需要我们将所有的动作都记录在日志中,并能在系统死机之后,重新调用这些动作恢复到之前的状态。通过新增两个方法( store()、load() ),命令模式就能够支持这一点。

五、总结

对于命令模式,书中介绍了一些要点:

  1. 命令模式将发出请求的对象和执行请求的对象解耦。
  2. 在被解耦的两者之间是通过命令对象进行沟通的。命令对象封装了接收者和一个或一组动作。
  3. 调用者通过调用命令对象的execute()发出请求,这会使得接收者的动作被调用。
  4. 调用者可以接受命令当做参数,甚至在运行时动态的进行。
  5. 命令可以支持撤销,做法是实现一个undo()方法来回到execute()被执行前的状态。
  6. 宏命令是命令的一种简单的延伸,允许调用多个命令。宏方法也可以支持撤销。
  7. 实际操作时,很常见使用“聪明”命令对象,也就是直接实现了请求,而不是将工作委托给接收者。
  8. 命令也可以用来实现日志和事务系统。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值