设计模式之命令模式

命令模式

定义

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

一个命令对象通过在特定接收者上绑定一组动作来封装一个请求。要达到这一点,命令对象将动作和接收者包进对象。这个对象只暴露出一个execute()方法,当此方法被调用的时候,接收者就会进行这些动作。从外面看,其他对象不知道空间哪个接收者进行了哪些动作,只知道如果调用了execute()方法,请求的目的就能达到。

类图

在这里插入图片描述

实现

1. 定义Command接口

/**
 * 命令定义
 */
public interface Command {
    /**
     * 执行命令
     */
    void execute();

    /**
     * 回退命令
     */
    void undo();
}

2. 定义命令接口实现类

/**
 * 包装开灯、关灯指令
 * ConcreteCommand
 */
public class LightOffCommand implements Command {
    Light light;
    public LightOffCommand(Light light) {
        this.light = light;
    }

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

    @Override
    public void undo() {
        light.on();
    }
}
/**
 * 包装开灯、关灯指令
 * ConcreteCommand
 */
public class LightOnCommand implements Command {
    Light light;
    public LightOnCommand(Light light) {
        this.light = light;
    }

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

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

3.定义命令接收者

/**
 * Receiver
 * 具体指令执行者
 */
public class Light {

    public void on() {
        System.out.println("Light ON.");
    }
    public void off() {
        System.out.println("Light OFF.");
    }
}

4. 定义调用者

/**
 * 使用命令对象
 * 假设只有一个遥控器,它只有一个按钮和对应的插槽,可以控制一个装置。
 * Invoker
 */
public class RemoteControl {
    // 有一个插槽持有命令,而这个命令控制着一个着墨
    private Command[] onCommands;
    private Command[] offCommands;
    private 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 onComand, Command offCommand) {
        onCommands[slot] = onComand;
        offCommands[slot] = offCommand;
    }

    /**
     * 当按下按钮时,这个方法就会被调用。使得当前命令衔接插槽,并调用它的`execute()`方法
     */
    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();
        System.out.println("UNDO");
    }

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

5. 定义Client

public class M {
    /**
     * M 为命令模式的客户。Client
     */
    public static void main(String[] args) {
        // 遥控器就是调用者,会传入一个命令对象,可以用来发出请求
        RemoteControl remoteControl = new RemoteControl();
        // 现在创建了一个电灯对象,此对象也是请求的接收者
        Light light = new Light();
        // 创建一个命令,并将接收者传递给命令
        LightOnCommand lightOnCommand = new LightOnCommand(light);
        LightOffCommand lightOffCommand = new LightOffCommand(light);

        // 模拟按下按钮
        // 在简单的遥控器中,我们先用一个 `打开电灯`命令加载按钮插槽,后来替换为`打开车库电灯`命令。
        // 遥控器插槽并根本不在乎所拥有的是什么命令对象。只要该命令对象实现了`command`接口就可以。
        GarageDoor garageDoor = new GarageDoor();
        GarageDoorOnCommand garageDoorOnCommand = new GarageDoorOnCommand(garageDoor);
        GarageDoorOffCommand garageDoorOffCommand = new GarageDoorOffCommand(garageDoor);

        remoteControl.setCommand(0, lightOnCommand, lightOffCommand);
        remoteControl.setCommand(1, garageDoorOnCommand, garageDoorOffCommand);

        remoteControl.onButtonWasPushed(0);
        remoteControl.onButtonWasPushed(1);
        remoteControl.offButtonWasPushed(0);
        remoteControl.undoButtonWasPushed();
        remoteControl.offButtonWasPushed(1);

    }
}

带状态回滚

public class CeilingFanOffCommand implements Command {
    CeilingFan ceilingFan;
    // 增加局部状态变量,以便追踪吊扇之前的速度
    int prevSpeed;
    public CeilingFanOffCommand(CeilingFan ceilingFan) {
        this.ceilingFan = ceilingFan;
    }

    @Override
    public void execute() {
        // 先将它的状态记录下来,以便需要撤销的时候用到
        prevSpeed = ceilingFan.getSpeed();
        ceilingFan.off();
    }

    @Override
    public void undo() {
        if (prevSpeed == 3) {
            ceilingFan.high();
        } else if (prevSpeed == 2) {
            ceilingFan.medium();
        } else if (prevSpeed == 1) {
            ceilingFan.low();
        } else {
            ceilingFan.off();
        }
    }
}

总结

  1. 命令模式可以将动作的请求者动作的执行者对象中解耦。
  • 遥控器应该知道如何解读按钮被按下的动作,然后发出正确的请求。但是不知道这些家电自动化的细节。
  • 使用状态实现撤销。
  • 命令模式更多用途: 队列请求。命令可以将运算块打包(一个接收者和一组动作),然后将它传来传去。就像是一般的对象一样。利用这样的特性衍生出: 日程安排(Scheduler)、线程池、工作队列。
  • 在某一端添加命令,然后另一端则是线程。线程进行如下操作: 从队列中取出一个命令,调用它的execute()方法,等待这个调用完成,然后将此命令对象丢弃,再取出下一个命令。
  1. 每个设计模式都应该由两部分组成: 一是应用场景、二是解决方案。如果只关注代码实现,会产生大部分设计模式看起来很相似的感觉。实际上,设计模式之间的主要区别在于设计意图,即应用场景。
  2. 命令模式VS策略模式
    1. 策略模式包含策略的定义、创建和使用三部分。从代码角度看,非常类似于工厂模式。区别在于,策略模式侧重“策略”或“算法”这个特定的应用场景,用来解决根据运行时状态从一组策略中选择不同策略的问题,而工厂模式侧重封装对象的创建过程,这里的对象没有任何业务场景的限定,可以是策略,但也可以是其他东西。从设计意图上来,这两个模式完全是两回事儿。
    2. 命令模式的主要作用和应用场景,是用来控制命令的执行,比如,异步、延迟、排队执行命令、撤销重做命令、存储命令、给命令记录日志等等,这才是命令模式能发挥独一无二作用的地方。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值