聊聊命令模式

聊聊命令模式

介绍

命令模式(Command Pattern),将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化。命令模式可将“动作请求者”从“动作执行者”对象中解耦。

网络上面的文章,大部分都是对它这么定义的。说实话很抽象,理解难度较大。

不过你换个角度去想一下,就会比较好理解。如果你使用过Linux,并且使用过它的命令,就会发现很将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化这一句话的意思了。

为什么呢?很简单,举个例子:比如在Linux中,我想解压一个tar压缩包,使用命令tar -cf ncov.tar ncov 就可以完成。但后来,我又想把显示压缩的一些文件列表,于是我又加上了一个参数-v,于是它就成了这样:tar -cvf ncov.tar ncov

按照,上面举的一个例子,只需要仔细想一下,其实很容易就能理解命令模式。

由于命令模式属于不是很常用的设计模式,在学习它的过程中,想到接触过的项目中,暂时还没有一个合适的业务场景去使用这种设计模式,那就先按照《Head Frist 设计模式》中的一些案例来进行理解。

书中案例

现在有一种遥控器,遥控器很多种按钮,每种按钮都有不同的功能。利用命令对象,把请求(例如打开电灯)封装成一个让特定的对象(例如客厅电灯对象)。所以,如果每个按钮都存储一个命令对象,那么当按钮按下的时候,就可以请命令对象做相关的工作。

实现命令接口:

public interface Command {
    void execute();
}

实现一个打开电灯的命令

现在,假设想实现一个打开电灯的命令。根据厂商所提供的类,Light有两个方法:on()和off()。

public class LightOnCommand implements Command {
    private Light light;

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

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

使用命令对象

既然是遥控器,那就需要一个遥控器的对象:

public class SimpleRemoteControl {
    private Command slot;

    public void setSlot(Command slot) {
        this.slot = slot;
    }
    public void buttonWasPressed() {
        slot.execute();
    }
}

测试使用

遥控器和开关命令都实现了,那么现在就来一个测试类测试一下:

public class RemoteControlTest {
    public static void main(String[] args) {
        SimpleRemoteControl simpleRemoteControl = new SimpleRemoteControl();
        Light light = new Light();
        LightOnCommand lightOnCommand = new LightOnCommand(light);

        simpleRemoteControl.setSlot(lightOnCommand);
        simpleRemoteControl.buttonWasPressed();
    }
}

现在我们实现了一个简单遥控器,但大部分遥控器都有很多按钮,不同的按钮对应着不同的操作。

那么我们现在来实现一个有多个按钮的遥控器:

public class RemoteControl {
    private final int slotSize = 2;
    private final Command[] onCommands;
    private final Command[] offCommands;

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

        // 初始化时的按钮都没有任何命令
        Command noCommand = new NoCommand();
        for (int i = 0; i < slotSize; i++) {
            onCommands[i] = noCommand;
            offCommands[i] = noCommand;
        }
    }


    public void setCommand(int slot, Command onCommand, Command offCommand) {
        // 边界检查
        if (slot >= 0 && slot < slotSize) {
            onCommands[slot] = onCommand;
            offCommands[slot] = offCommand;
        }
    }

    public void onButtonWasPushed(int slot) {
        if (slot >= 0 && slot < slotSize) {
            onCommands[slot].execute();
        }
    }

    public void offButtonWasPushed(int slot) {
        if (slot >= 0 && slot < slotSize) {
            offCommands[slot].execute();
        }
    }
}

关灯命令:

public class LightOffCommand implements Command {
    private final Light light;

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

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

测试:

现在,遥控器上一共有两个按钮,一个是房间的灯,一个是客厅的灯。

public static void main(String[] args) {
    RemoteControl remoteControl = new RemoteControl();
    Light livingRoomLight = new Light("客厅");
    Light roomLight = new Light("房间");
    
    LightOnCommand livingRoomLightOnCommand = new LightOnCommand(livingRoomLight);
    LightOffCommand livingRoomLightOffCommand = new LightOffCommand(livingRoomLight);
    LightOnCommand roomLightOnCommand = new LightOnCommand(roomLight);
    LightOffCommand roomLightOffCommand = new LightOffCommand(roomLight);
    
    remoteControl.setCommand(0, livingRoomLightOnCommand, livingRoomLightOffCommand);
    remoteControl.setCommand(1, roomLightOnCommand, roomLightOffCommand);
    
    remoteControl.onButtonWasPushed(0);
    remoteControl.onButtonWasPushed(1);
    remoteControl.offButtonWasPushed(0);
    remoteControl.offButtonWasPushed(1);
}

好了,这样就实现了一个多个按钮的遥控器,遥控器具体是怎么执行的,我们无需关注。只需要知道能满足我们的要求即可。

虽然,我们实现了一个多个按钮的遥控器。但它还是不够高级,不支持将命令组合起来使用。

Party模式

我现在想要开灯,要求一键开启客厅和房间的灯,关灯也是如此。

这里,我们想要实现这种功能,就要使用宏命令模式,把按钮的功能组合进去。

public class MacroCommand implements Command {
    private final Command[] commands;

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

    @Override
    public void execute() {
        for (Command command : commands) {
            command.execute();
        }
    }
}

使用:

public static void main(String[] args) {
    RemoteControl remoteControl = new RemoteControl();
    Light livingRoomLight = new Light("客厅");
    Light roomLight = new Light("房间");

    Command[] onCommands = {new LightOnCommand(livingRoomLight), new LightOnCommand(roomLight)};
    Command[] offCommands = {new LightOffCommand(livingRoomLight), new LightOffCommand(roomLight)};
    MacroCommand macroOnCommand = new MacroCommand(onCommands);
    MacroCommand macroOffCommand = new MacroCommand(offCommands);
    remoteControl.setCommand(0, macroOnCommand, macroOffCommand);

    remoteControl.onButtonWasPushed(0);
    remoteControl.offButtonWasPushed(0);
}

这样就实现了宏命令的模式。就像开头列举的一个例子,想要打印出文件列表,只需要加上一个-v参数就好了。

使用宏命令开关灯也是一样,比如想要开厨房的灯,和关厨房的灯,把厨房的命令加上就可以完成我们的需求。

这就是本篇文章要介绍的命令模式,使用场景确实感觉会比较少,不过对于一些执行操作耦合太紧时,或者遇到大量的if else,可以尝试使用命令模式。

不过,说到if else明明策略模式就可以解决,为什么还要使用命令模式呢?

在工作中很多设计模式都是结合着来使用的,用来解耦、提高编码素质、和代码的可读性等等。

设计模式虽好,但也不要为了使用设计模式而去过渡的设计,可能会造成明明很简单的一个功能,却因为过度设计而带来了不必要的复杂性。

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×