《Head-First-设计模式》笔记8-命令模式

定义

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

在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计,使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活。

命令模式可以对发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。这就是命令模式的模式动机。

常见的情景

在餐厅点餐工作流程中,顾客将写好的订单看做一个命令,服务员接受了这个命令,然后他把订单(命令)交给厨师,厨师再根据订单(命令)去做菜。在这个情景中,顾客是动作请求者,厨师是动作执行者,二者并没有接触(解耦),但是通过命令的传递完成了整个点菜流程。

模式结构

命令模式包含如下角色:

  • Command: 抽象命令类
  • ConcreteCommand: 具体命令类
  • Invoker: 调用者
  • Receiver: 接收者
  • Client:客户类

命令模式类图

从餐厅到命令模式

对应关系:

  • 女招待————Invoker
  • 厨师————Receiver
  • 顾客————Client
  • 订单————Command
  • orderUp()————execute()
  • takeOrder()————setComand()

简单实例代码

命令接口

public interface Command {
    public void execute();
}

电灯及打开电灯命令

// 电灯
public class Light {
    public void on() {
        System.out.println("on");
    }

    public void off() {
        System.out.println("off");
    }
}

// 打开电灯命令
public class LightOnCommand implements Command {
    Light light;

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

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

使用命令对象

public class SimpleRemoteControl {
    Command slot;

    public void setCommand(Command command) {
        slot = command;
    }

    public void buttonWasPressed() {
        slot.execute();
    }
}

测试

public class RemoteControTest {
    public static void main(String[] args) {
        SimpleRemoteControl remote = new SimpleRemoteControl();
        Light light = new Light();
        LightOnCommand lightOn = new LightOnCommand(light);

        remote.setCommand(lightOn);
        remote.buttonWasPressed();
    }
} /*output:
on
*/

支持撤销,多个命令实例代码

命令接口

public interface Command {
    public void execute();
    public void undo(); //增加撤回功能
}

打开电灯命令

电灯类不变

public class LightOnCommand implements Command {
    Light light;

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

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

    @Override
    public void undo() {
        System.out.println("undo");
        light.off();
    }
}

关灯命令

public class LightOffCommand implements Command {
    Light light;

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

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

    @Override
    public void undo() {
        System.out.println("undo");
        light.on();
    }
}

NoCommand

NoCommand 对象是一个空对象(null object)的例子。当你不想返回一个有意义的对象时,空对象就很有用。客户也可以将处理 null 的责任转移给空对象。举例来说,遥控器不可能一出厂就设置了有意义的命令对象,所以提供了 Nocommand 对象作为代用品,当调用它的 execute() 方法时这种对象什么事情都不做。

在许多设计模式中,都会看到空对象的使用。甚至有些时候,空对象本身也被视为是一种设计模式。

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

    @Override
    public void undo() { }
}

使用命令对象(遥控器)

遥控器

public class RemoteControl {
    Command[] onCommands; // 多个命令
    Command[] offCommands;
    Command undoCommand;

    public RemoteControl() {
        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 onButtonWasPressed(int slot) {
        onCommands[slot].execute();
        undoCommand = onCommands[slot];
    }

    public void offButtonWasPressed(int slot) {
        offCommands[slot].execute();
        undoCommand = offCommands[slot];
    }

    public void undoButtonWasPressed() {
        undoCommand.undo();
    }
}

风扇(多种状态)

风速不同,多种设置。

public class CeilingFan {
    public static final int HIGH = 3;
    public static final int MEDIUM = 2;
    public static final int LOW = 1;
    public static final int OFF = 0;
    int speed;

    public CeilingFan() { speed = OFF; }
    public void high() { speed = HIGH; }
    public void medium() { speed = MEDIUM; }
    public void low() { speed = LOW; }
    public void off() { speed = OFF; }
    public int getSpeed() { return speed; }
}

风扇高档命令

风扇其他命令省略。

public class CeilingFanHighCommand implements Command {
    CeilingFan ceilingFan;
    int prevSpeed;

    public CeilingFanHighCommand(CeilingFan ceilingFan) {
        this.ceilingFan = ceilingFan;
    }

    @Override
    public void execute() {
        prevSpeed = ceilingFan.getSpeed();
    }

    @Override
    public void undo() {
        if (prevSpeed == CeilingFan.HIGH) {
            ceilingFan.high();
        } else if (prevSpeed == CeilingFan.MEDIUM) {
            ceilingFan.medium();
        } else if (prevSpeed == CeilingFan.LOW) {
            ceilingFan.low();
        } else if (prevSpeed == CeilingFan.OFF) {
            ceilingFan.off();
        }
    }
}

多个命令

测试时可以使用两个 Command[],一个存放 on 命令,另一个存放 off 命令。可同时执行多个命令。

public class MacroCommand implements Command {

    Command[] commands;
    public MacroCommand(Command[] commands) {
        this.commands = commands;
    }

    @Override
    public void execute() {
        for (int i = 0; i < commands.length; i++) {
            commands[i].execute();
        }
    }

    @Override
    public void undo() {
        for (int i = 0; i < commands.length; i++) {
            commands[i].undo();
        }
    }
}

测试

public class RemoteControTest {
    public static void main(String[] args) {
        RemoteControl remote = new RemoteControl();
        Light light = new Light();
        LightOnCommand lightOn = new LightOnCommand(light);
        LightOffCommand lightOff = new LightOffCommand(light);

        remote.setCommand(0, lightOn, lightOff);

        remote.onButtonWasPressed(0);
        remote.offButtonWasPressed(0);
        remote.undoButtonWasPressed();
    }
}/*output:
on
off
undo
on
*/

命令模式的其他用途

  • 队列请求:有一个工作队列:你在某端添加命令,然后另一端是线程。线程进行下面的动作:从队列中取出一个命令,调用它的 execute() 方法,等待这个调用完成,然后将此命令对象丢弃,再取出下一个命令;
  • 日志请求: 某些应用需要我们将所有的动作都记录到 日志中, 并能在系统死机后,重新调用这些动作恢复到之前的状态。how to do that? 当我们执行命令的时候,将历史记录存储在磁盘中。一旦系统死机,我们就可以将命令对象重新加载,并成批次地调用这些对象的 execute 方法;

优缺点

命令模式的优点

  • 降低系统的耦合度。
  • 新的命令可以很容易地加入到系统中。
  • 可以比较容易地设计一个命令队列和宏命令(组合命令)。
  • 可以方便地实现对请求的 Undo 和 Redo。

命令模式的缺点

  • 使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用。


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 bin07280@qq.com

文章标题:《Head-First-设计模式》笔记8-命令模式

文章字数:1.5k

本文作者:Bin

发布时间:2018-07-11, 20:18:55

最后更新:2019-08-06, 00:07:35

原始链接:http://coolview.github.io/2018/07/11/Head-First-%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E3%80%8AHead-First-%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E3%80%8B%E7%AC%94%E8%AE%B08-%E5%91%BD%E4%BB%A4%E6%A8%A1%E5%BC%8F/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录