设计模式大总结(六):命令模式

前言

最近看了命令模式,觉得很有意思,平时大家应该都有使用过,所以写一点总结体会分享给大家。

正文

首先我们先不谈什么是命令模式,直接写点东西:

实现一个电视遥控器的功能:
1、遥控器有两个键:开机键和关机键。
2、电视接收对应的命令信号,执行对应的操作。

ok,首先我们知道命令是一个抽象的概念,所以我们先写一个Command借口:

/**
 * Created by li.zhipeng on 2017/9/26.
 *      命令接口
 */
public interface Command {

    void onCommand();
}

因为遥控器要绑定对应的电视机,所以我们的遥控器按钮构造方法里或者是setter能够设置绑定的电视机,这样我们创建一个基类,我们的目的是为了在基类复杂一些基本信息,例如按钮的位置,颜色等公有特性:

/**
 *      遥控器按钮的基类
 */
public abstract class ControllerButton {

    private TV tv;

    public ControllerButton(TV tv){
        this.tv = tv;
    }

}

下面定义开机键和关机键的功能:

/**
 * 开机功能
 */
public class OpenCommand implements Command {
    @Override
    public void onCommand() {
        System.out.println("打开电视...");
        tv.open();
    }
}

/**
 * 关机功能
 */
public class CloseCommand implements Command {
    @Override
    public void onCommand() {
        System.out.println("关闭电视...");
        tv.close();
    }
}

使用时的代码:

TV tv = new TV();

Command openCommand = new OpenCommand(tv);
Command closeCommand = new CloseCommand(tv);

openCommand.onCommand();
closeCommand.onCommand();

这里写图片描述

功能的扩展

刚才我们已经完成了基本功能的开发,我们的类的定义还是比较严格的,基类和接口都只做自己相关的事情,我们定义的每一个类都尽可能的少承担起整个功能的责任,符合我们开发的基本原则。

例如,我们可以去掉Command接口,把onCommand移动到基类ControllerButton中,但是这样就加重了基类的负担,违背了我们开发的基本准则,也影响到了之后的功能更扩展,所以不建议这样做的。

但是很快新需求来了:

在发送命令的时候,需要同时发送一条指示灯闪闪闪的命令,这样用户知道自己的命令到底发没发出去,有助于用户体验。

现在只有两个按钮,我们只要在调用的时候,添加上指示灯闪闪的命令就可以了:

// 红灯闪闪命令
lampBulingCommand.onCommand();
openCommand.onCommand();

这个时候一定有人站出来了:

我现在就要把Command去掉,直接在基类ControllerButton里面实现onCommand方法,这样不用修改调用方法,比你这种实现吊太多!!!

不得不说这是一个好办法,以现在这个需求来看修改基类是最简单的,但是违背了我们之前的开发原则,但是为了这位朋友的任性,我们暂时不阻止他,于是基类的代码发生了改变:

/**
 *      遥控器按钮的基类
 */
public abstract class ControllerButton implements Command{

    protected TV tv;

    /**
     * 指示灯闪闪命令
     * */
    private LampBulingCommand lampBulingCommand;

    public ControllerButton(TV tv){
        this.tv = tv;
        this.lampBulingCommand = lampBulingCommand;
    }

    /**
     * 对onCommand进行包装
     * */
     public void sendCommand(){
        if (lampBulingCommand != null){
            lampBulingCommand.onCommand();
        }
        onCommand();
    }

}

// 调用处,请注意这里已经无法Command,因为sendCommand定义在基类里,而不是在Command接口里
OpenCommand openCommand = new OpenCommand(tv);
openCommand.sendCommand();

我们看到了,为了实现产品的需求,他做了3处修改:

1、添加属性,保存指示灯闪闪命令。
2、修改构造方法,实例化指示灯闪闪命令。
3、在onCommand外部定义了一个中转函数,执行指示灯闪闪命令。
4、修改调用的代码。
5、最关键:已经无法通过Command类型创建方法。

但是他仍然陶醉在自己的世界里,认为这个方法屌爆了。

但是很遗憾,很快新需求又来了:

在新增一条记忆命令,我们需要统计开机的次数,但是不统计关机命令的次数。

虽然这个需求有点变态了,但是我们还得硬着头皮继续写,这个时候刚才的那位朋友又站出来了,分分钟就要解决这个问题:

/**
 *      遥控器按钮的基类
 */
public abstract class ControllerButton implements Command{

    protected TV tv;

    /**
     * 指示灯闪闪命令
     * */
    private LampBulingCommand lampBulingCommand;

    /**
    * 记忆命令
    */
    private MemoryCommand memoryCommand;

    public ControllerButton(TV tv){
        this.tv = tv;
        this.lampBulingCommand = lampBulingCommand;
        memoryCommand = new MemoryCommand(tv);
    }

    /**
     * 对onCommand进行包装
     * */
     public void sendCommand(){
        // 指示灯闪闪
        lampBulingCommand.onCommand();
        // 如果是开机功能,要发送记忆命令
        if (this instanceof OpenCommand) {
            memoryCommand.onCommand();
        }
        onCommand();
    }

}

经过修改后的代码,虽然运行正常,他自己已经感觉到自己挖的坑越来越深,而其他人也出现了怀疑的态度,因为:

1、父类已经开始影响到子类的业务逻辑。

2、基类越来越臃肿:每一次内部的属性的增加和sendCommand方法的复杂度的上升,都让基类的变得越来越臃肿,并且基类已经开始越权处理onCommand的逻辑,Command接口已经形同虚设,类的阅读和维护都开始出现了问题。

3、已经无法通过Command创建命令实例,全部要被替换成ControllerButton,类的语义出现了严重的危机。

命令模式登场

经过之前的讨论,命令模式终于登场了,于是咔咔咔重构了代码:

/**
 * 命令的执行者,处于命令发起者和接收者之间,在这个过程中进行处理
 */
public class Switch {

    private LampBulingCommand lampBulingCommand;

    private MemoryCommand memoryCommand;

    public Switch(TV tv) {
        lampBulingCommand = new LampBulingCommand(tv);
        memoryCommand = new MemoryCommand(tv);
    }

    public void excuteCommand(Command command) {
        // 指示灯闪闪
        lampBulingCommand.onCommand();
        // 如果是开机功能,要发送记忆命令
        if (command instanceof OpenCommand) {
            memoryCommand.onCommand();
        }
        // 执行参数命令
        command.onCommand();
    }

}

修改调用的代码:

TV tv = new TV();
Command openCommand = new OpenCommand(tv);
Command closeCommand = new CloseCommand(tv);
//openCommand.onCommand();
//closeCommand.onCommand();
Switch s = new Switch(tv);
s.excuteCommand(openCommand);
s.excuteCommand(closeCommand);

是不是完全被惊艳到了,就是简单~

现在开始进入正题:什么是命令模式

命令模式是把一个操作和他的参数,包装成一个新的对象去执行。
命令模式有四个部分:命令,接收者,执行者,发起者。

以刚才的demo为例,Command代表命令,接收者是TV,执行者是Switch,发起者也就是客户端。

从demo中看到,随着需求的变化,我们的每一次修改都要修改多个类,并且代码的成本也很高,于是通过Switch把Command的执行过程包装了起来,也就是在发起者和接收者之间,随意我们就可以根据需求,定制执行的过程。

也可以理解成,Switch把基类中有关于Command的功能全部抽取了出来,作为一个独立模块。

经过命令模式的重构,我们之后的扩展和修改,只要不改变open和close的核心功能,只要修改Switch类就可以了,这就是命令模式的优点。

总结

最后我们对命令模式进行一下总结:

1、命令模式是对某一个操作的和其参数的封装,目的是维护这个操作的过程。

2、命令模式位于发起者和接收者之间,对两者进行解耦,便于维护。

3、命令模式能够帮助我们明确类和接口的定义的目的,理解面向对象编程。

顺便强调一下,过多的if-else是糟糕的代码,凸显出程序的笨重,例如demo中,我们可以利用开关来解决:

/**
*  在基类中增加boolean型开关,并增加参数为ControllerButton的方法
*/
public class Switch {

    ...

    public void excuteCommand(ControllerButton controllerButton) {
        // 如果是开机功能,要发送记忆命令
        if (controllerButton.isNeedMemory()) {
            memoryCommand.onCommand();
        }
        excuteCommand(controllerButton);
    }

    public void excuteCommand(Command command) {
        // 指示灯闪闪
        lampBulingCommand.onCommand();
        // 执行参数命令
        command.onCommand();
    }

}

最后要说的是,不要随意违背开发的基本原则,例如上面的那位朋友,这些规则是前辈经过长时间的研究总结的结晶,当然这些经验不一定是对的,也不适于所有的场景,但是如果你非要这么做,请做好充足的准备。

猜你喜欢

转载自blog.csdn.net/u011315960/article/details/78095437