[设计模式笔记] No.6 命令模式(Command)

封装“方法调用”,将“发出请求的对象”和“接收与执行这些请求的对象”分隔开来。

命令模式

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

生活中的命令模式——餐厅点餐

① 你,也就是顾客,把订单交给女招待;
②女招待拿了订单,放在订单柜台,然后喊了一声“订单来了”;
③快餐厨师根据订单准备餐点。

思考:对象和方法的调用关系
①顾客知道他要的是什么,他创建一张订单 createOrder();
②女招待拿走了订单 takeOrder(),放在订单柜台,然后调用 orderUp() 方法,通知厨师准备点餐;
③快餐厨师根据指令准备餐点。

继续分析(文字有点多,请耐心阅读)
①一张订单封装了准备餐点的请求

把订单想象成一个用来请求准备餐点的对象,和一般的对象一样,订单对象可以被传递:从女招待传递到订单柜台,或者从女招待传递到接替下一班的女招待。
订单的接口包含一个方法,也就是 orderUp()。这个方法封装了准备餐点所需的动作。订单内有一个到“需要进行准备工作的对象”(也就是厨师)的引用
这一切都被封装起来,所以女招待不需要知道订单上有什么,也不需要知道是谁来准备餐点;她只需要将订单放到订单窗口,然后喊一声“订单来了”就可以了。

②女招待的工作是接收订单,然后调用订单的orderUp()方法。

女招待的工作很简单:接下顾客的订单,继续帮助下一个顾客,然后将一定数量的订单放到订单柜台,并调用orderUp()方法,让人来准备餐点。她不必担心订单的内容是什么,或者由谁来准备餐点。她只需要知道,订单有一个orderUp()方法可以调用,这就够了
不同的顾客有不同的订单,这会使得女招待的takeOrder()方法被传入不同的参数,女招待知道所有的订单都支持orderUp()方法,任何时候他需要准备餐点是,调用这个方法就是了。

③快餐厨师具备准备餐点的知识。

一旦女招待调用orderUp()方法,快餐厨师就接手,厨师具有创建餐点的所有方法。
请注意,女招待和厨师之间是彻底解耦的:女招待的订单封装了餐点的细节,她只要调用每个订单的方法即可,而厨师看了订单就知道该做些什么餐点;厨师和女招待之间从来不需要直接沟通。

命令模式类图
[设计模式笔记] No.6 命令模式(Command)_第1张图片
command_01.jpg

呃。。。上面的类图还是有点难看懂,没事,完成下面的案例后,再回头看看这个类图,你就清晰了。

需求

你们公司需要开发一套系统,运用于家电控制的遥控器。这个遥控器有4个按钮,两排两列,分别是对应于控制灯的开关,车库门的开关,另外还有一个撤销按钮(undo)。
类似下图:


[设计模式笔记] No.6 命令模式(Command)_第2张图片
command_02.jpg
①实现命令接口——Command

首先,让所有的命令对象实现相同的包含一个方法的接口。在餐厅的例子中,我们称此方法为orderUp(),然而,现在改为一般惯用的名称excute().

public interface Command {

    /**
     * 执行
     */
    void excute();

    /**
     * 撤销
     */
    void undo();
}
②具体的命令

开灯命令——LightOnCommand

public class LightOnCommand implements Command {
    private Light mLight;

    public LightOnCommand(Light light) {
        mLight = light;
    }

    @Override
    public void excute() {
        mLight.on();
    }

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

关灯命令——LightOffCommand

public class LightOffCommand implements Command {
    private Light mLight;

    public LightOffCommand(Light light) {
        mLight = light;
    }

    @Override
    public void excute() {
        mLight.off();
    }

    @Override
    public void undo() {
        mLight.on();
    }
}

开车库门命令——GarageDoorUpCommand

public class GarageDoorUpCommand implements Command {
    private GarageDoor mGarageDoor;

    public GarageDoorUpCommand(GarageDoor garageDoor) {
        mGarageDoor = garageDoor;
    }

    @Override
    public void excute() {
        mGarageDoor.up();
    }

    @Override
    public void undo() {
        mGarageDoor.down();
    }
}

关车库门命令——GarageDoorDownCommand

public class GarageDoorDownCommand implements Command {
    private GarageDoor mGarageDoor;

    public GarageDoorDownCommand(GarageDoor garageDoor) {
        mGarageDoor = garageDoor;
    }

    @Override
    public void excute() {
        mGarageDoor.down();
    }

    @Override
    public void undo() {
        mGarageDoor.up();
    }
}
③遥控器 控制类——RemoteControlWithUndo
public class RemoteControlWithUndo {

    private List mOnCommands = new ArrayList<>();//开
    private List mOffCommands = new ArrayList<>();//关
    private Command mUndoCommand;//撤销

    public RemoteControlWithUndo() {
        NoCommand noCommand = new NoCommand();
        for (int i = 0; i < 2; i++) {
            mOnCommands.add(noCommand);
            mOffCommands.add(noCommand);
        }
//        一开始,并没有所谓的“前一个命令”,所以将它设置为NoCommand的对象。
        mUndoCommand = noCommand;
    }

    /**
     * 给按钮设置命令
     *
     * @param slot       位置,列
     * @param onCommand  开命令
     * @param offCommand 关命令
     */
    public void setCommand(int slot, Command onCommand, Command offCommand) {
        mOnCommands.set(slot, onCommand);
        mOffCommands.set(slot, offCommand);
    }

    /**
     * 点击某一列的 开 按钮
     *
     * @param slot 位置,列
     */
    public void onButtonWasPushed(int slot) {
        mOnCommands.get(slot).excute();
        mUndoCommand = mOnCommands.get(slot);
    }

    /**
     * 点击某一列的 关 按钮
     *
     * @param slot 位置,列
     */
    public void offButtonWasPushed(int slot) {
        mOffCommands.get(slot).excute();
        mUndoCommand = mOffCommands.get(slot);
    }

    /**
     * 撤销,返回前一个命令 的操作
     */
    public void undoButtonWasPushed() {
        mUndoCommand.undo();
    }
}

在遥控器中我们不想每次都检查是否某个按钮下面都加载了命令,比方说,在onButtonWasPushed()方法中,我们可能需要这样的代码

public void onButtonWasPushed(int slot) {
        if (mOnCommands.get(slot) != null) {
            mOnCommands.get(slot).excute();
            mUndoCommand = mOnCommands.get(slot);
        }
    }

所以我们在RemoteControlWithUndo的构造器中,每个按钮先指定成NoCommand对象,以便每个按钮永远都有命令对象。

NoCommand noCommand = new NoCommand();
        for (int i = 0; i < 2; i++) {
            mOnCommands.add(noCommand);
            mOffCommands.add(noCommand);
        }
//        一开始,并没有所谓的“前一个命令”,所以将它设置为NoCommand的对象。
        mUndoCommand = noCommand;

NoCommand对象

public class NoCommand implements Command {
    @Override
    public void excute() {
        System.out.println("No Command");
    }

    @Override
    public void undo() {
        System.out.println("No Command");
    }
}
④测试
public class TestCommand {

    public static void main(String args[]) {
        RemoteControlWithUndo remoteControlWithUndo = new RemoteControlWithUndo();
        Light light = new Light();//灯
        LightOnCommand lightOnCommand = new LightOnCommand(light);//开灯命令
        LightOffCommand lightOffCommand = new LightOffCommand(light);//关灯命令
        remoteControlWithUndo.setCommand(0, lightOnCommand, lightOffCommand);//将命令设置到调用者中

        GarageDoor garageDoor = new GarageDoor();//车库门
        GarageDoorUpCommand garageDoorUpCommand = new GarageDoorUpCommand(garageDoor);//开车库门命令
        GarageDoorDownCommand garageDoorDownCommand = new GarageDoorDownCommand(garageDoor);//关车库门命令
        remoteControlWithUndo.setCommand(1, garageDoorUpCommand, garageDoorDownCommand);//

        remoteControlWithUndo.onButtonWasPushed(0);
        remoteControlWithUndo.offButtonWasPushed(0);
        remoteControlWithUndo.onButtonWasPushed(1);
        remoteControlWithUndo.offButtonWasPushed(1);

        remoteControlWithUndo.undoButtonWasPushed();//撤销,返回上一个命令
    }
}

结果

Light is On
Light is Off
GarageDoor is Up
GarageDoor is down
GarageDoor is Up
小知识

NoCommand对象是一个空对象(null object)的例子。当你不想返回一个有意义的对象时,空对象就很有用。客户也可以将处理null的责任转移给空对象。举例来说,遥控器不可能一出场就设置了有意义的命令对象,所以提供了NoCommand对象作为代用品,当调用它的excute()方法时,这种对象什么事情都不做。
在许多设计模式中,都会看到空对象的使用。甚至有些时候,空对象本身也被视为一种设计模式。

现在,你可以回过头去看看上面的 命令模式的类图,看看是否对命令模式更加理解了。

要点
  • 命令模式将 发出请求的对象执行请求的对象 解耦。
  • 在被解耦的两者之间是通过命令对象进行沟通的。命令对象封装了接收者和一个或一组动作。
  • 调用者通过调用命令对象的excute()发出请求,这会使得接收者的动作被调用。
  • 命令可以支持撤销,做法是实现一个undo()方法来回到excute()被执行前的状态。

感谢你的耐心阅读,命令模式基本知识和应用就介绍到这里了。

最后回顾一下

我们的设计工具箱中的工具
1、OO基础

① 抽象
② 封装
③ 多态
④ 继承

2、OO原则

① 封装变化
② 多用组合,少用继承
③ 针对接口编程,不针对实现编程
④ 为交互对象之间的松耦合设计而努力
⑤ 类应该对扩展开放,对修改关闭
⑥ 依赖抽象,不要依赖具体编程

3、OO模式

① 策略模式——定义算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。

② 观察者模式——在对象之间定义了一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象都会受到通知并自动更新。

③ 装饰者模式——动态地将责任附加到对象上,若要扩展功能,装饰者提供有别于继承的另一种选择。

④ 工厂方法模式——定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
抽象工厂模式——提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。

⑤单件模式——确保一个类只有一个实例,并提供全局访问点。

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

感谢阅读!
No.5 单件模式(singleton )
No.4 工厂模式(Factory)
No.3 装饰者模式(Decorator)
No.2 观察者模式(Observer)
No.1 策略模式(Strategy)
前言 为何要使用设计模式

Demo代码

你可能感兴趣的:([设计模式笔记] No.6 命令模式(Command))