设计模式 - D6 - 命令模式

封装调用

封装调用时指把运算块包装成形,而调用此运算的对象不需要关心事情是如何进行,只需如何使用包装成形的方法来完成即可。利用封装方法调用,可以完成一些记录日志等操作。因此,在此引入命令模式

命令模式

可将“发出请求的对象”从“接收与执行这些请求的对象”解耦定义

定义

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

  • 一个命令对象通过在特定接收者上绑定一组动作来封装一个请求,即将动作和接收者包进对象中;同时,命令对象只暴露出一个execute()方法,此方法被调用时,接收者就会进行这些操作,但对外来说,调用者不知道进行哪些操作,从而实现将“发出请求的对象”从“接收与执行这些请求的对象”解耦

在这里插入图片描述

  • Client:负责创建一个ConcreteCommand,并设置其包含的Receiver
  • Command:为所有命令声明了一个接口,调用Command对象的execute()方法就可以让Receiver进行相关的动作,undo()方法
  • ConcreteCommand:定义了动作和接受者之间的绑定关系,Invoker只要调用execute()发出请求,ConcreteCommand就会调用Revicer的一个或多个动作
  • Invoker:持有一个命令对象,在某个时刻调用命令的execute()方法
  • Receiver:执行其一个或多个动作,任何类都可以作为Receiver
    在这里插入图片描述

基本命令模式

假设我们现在要使用一个遥控器,在多个开关上控制多种电器进行不同的操作,那么这里遥控器的多个开关就相当于多个Invoker,多种电器就是多种Receiver,而绑定在开关上的操作就是Command

命令(Command)建立

命令接口
要实现一个命令对象,就必须先实现包含一个方法的命令接口,让所有命令对象实现相同的接口

public interface Command {
    void execute();
}

具体命令
假设要实现一个打开电灯的命令,而一个电灯类(Light)中有两个方法:on()和off(),则一个具体命令可以设计为如下
(电灯类其实就是一个接收者(Receiver))

public class LightOnCommand implements Command{
    private Light light;

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

    @Override
    public void execute() {
        light.on();
    }
}
public class Light {
    String name;

    public Light(String name) {
        this.name = name;
    }

    public void on() {
        System.out.println(name + " On");
    }

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

调用者(Invoker)建立

在这里,调用者就是遥控器,其实现可以是

public class SimpleRemoteControl {
    Command command;
    
    public void setCommand(Command command) {
        this.command = command;
    }
    
    public void buttonWasPressed() {
        command.execute();
    }
}

由上述代码可见,当我们需要更换命令操作时,无须对调用者的代码进行任何修改,只需创建新的命令对象,然后传入调用者即可

客户建立

在这里,客户用来测试调用者类的功能,它需要创建命令对象(LightOnCommand)、接收者(Light)和调用者(SimpleRemoteControl),然后分别向LightOnCommand和SimpleRemoteControl传入Light和LightOnCommand

public class RemoteControlTest {
    public static void main(String[] args) {
        // 创建invoker、receiver、command
        SimpleRemoteControl invoker = new SimpleRemoteControl();
        Light receiver = new Light("Light");
        LightOnCommand command = new LightOnCommand(receiver);
        
        // 向invoker传入command
        invoker.setCommand(command);
        
        // invoker.buttonWasPressed() -> command.execute() -> light.on()
        invoker.buttonWasPressed();
    }
}

注:空对象

当我们不想返回一个有意义的对象时,可以使用空对象,从而将处理null的责任转移给空对象。例如,当我们不想使用if (command == null)判断是否存在指令时,可以使用一个实现execute()方法为空方法的NoCommand对象代替

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

支持撤销的命令模式

命令(Command)

当命令支持撤销时,命令对象就必须加上撤销功能,也就是命令接口要同时提供execute()方法和undo()方法,不论execute()方法执行了什么操作,undo()方法都会进行相反的操作

public interface Command {
    void execute();
    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() {
        light.off();
    }
}

调用者(Invoker)

为了让调用者能追踪最后被调用命令,可以添加一个实例变量来记录最后被调用命令,然后当需要执行撤销指令时,就通过这个实例变量来调用其undo()方法(此处Invoker扩展为可以使用多个Command)
显然,如果希望undo可以回溯多个操作,则可以使用栈来记录过去所调用的命令

public class SimpleRemoteControl {
    private Command[] commands;
    private Command undoCommand;

    public SimpleRemoteControl() {
        commands = new Command[6];
        for(int i = 0; i < 6; i++) {
            commands[i] = new NoCommand();
        }
        undoCommand = new NoCommand();
    }

    public void setCommand(int slot, Command command) {
        this.commands[slot] = command;
    }

    public void buttonWasPressed(int slot) {
        commands[slot].execute();
        undoCommand = commands[slot];
    }

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

客户(Client)

因此,客户就可以修改为

public class RemoteControlTest {
    public static void main(String[] args) {
        // 创建invoker、receiver、command
        SimpleRemoteControl invoker = new SimpleRemoteControl();
        Light receiver = new Light("Light");
        LightOnCommand command = new LightOnCommand(receiver);

        // 向invoker传入command
        invoker.setCommand(1, command);

        // invoker.buttonWasPressed() -> command.execute() -> light.on()
        invoker.buttonWasPressed(1);
        invoker.undoButtonWasPressed();
    }
}

输出:
Light On
Light Off

支持宏命令的命令模式

当我们想要通过一个命令对象执行一组其他命令时,我们就可以创建一个宏命令模式。宏命令对象,其实就是带有一个命令对象数组/集合的、同时也实现了Command接口的命令对象

	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 RemoteControlTest {
    public static void main(String[] args) {
        // 创建invoker、receiver、command
        SimpleRemoteControl invoker = new SimpleRemoteControl();
        Light receiver = new Light("Light");
        LightOnCommand command = new LightOnCommand(receiver);
        Light receiver1 = new Light("Light1");
        LightOnCommand command1 = new LightOnCommand(receiver1);
        MacroCommand macroCommand = new MacroCommand(new Command[]{command, command1});

        // 向invoker传入command
        invoker.setCommand(0, macroCommand);

        // invoker.buttonWasPressed() -> command.execute() -> light.on()
        invoker.buttonWasPressed(0);
        invoker.undoButtonWasPressed();
    }
}

输出:
Light On
Light1 On
Light Off
Light1 Off

命令模式的用途

队列请求

命令可以将运算块打包(一个接收者和一组动作);此时,若有一个队列,我们可以在队列的一段添加队列,而队列另一端的线程可以从队列中取出一个命令,调用其execute()方法方法,当该方法调用完成时,就将该命令丢弃,再取出下一个命令
此时,工作队列类和进行计算的对象之间完全时解耦的,即队列此时可能在进行财务运算,下一时刻可能就在读取网络数据,它无须知道执行的具体操作是什么,只需知道执行命令的execute()方法即可

日志请求

通过Java的序列化(Serialization)在请求对象中添加store()和load()方法即可在执行命令时,将历史记录存储在磁盘中。当系统宕机时,就可以将命令对象重新加载,并成批地、依次地调用这些对象的execute()方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值