九、命令模式—使用命令控制奶茶店中酷炫的灯 #和设计模式一起旅行#

请不要用命令的口吻关系你在乎的人 — dufy

故事背景

在巴厘岛开的奶茶店开张后,生意很红火,每天都要忙到很晚,晚上就要打开奶茶店的酷炫的灯,由于安装了不同的灯,灯的开关都不在一个地方,那么需要打开和关闭所有的灯就很麻烦。作为老板的我,每天都要去开灯和关灯,这种粗活可不能交给设计模式MM去做啊。(pS:每个灯一个开关,并且不在一个地方,每次只能打开一个或者关闭一个灯)。

我将上面描述的事情用代码表示:

public class Boss {

    private String name;
    private KuXuanLight kuXuanLight = new KuXuanLight();
    public Boss(String name){
        super();
        this.name = name;
    }

    public void openLight(){
        System.out.println("----晚上了,天黑了,准备开灯---");

        kuXuanLight.open();

        System.out.println("----开灯完成---");
    }

    public void closeLight(){
        System.out.println("----下班了,准备关灯---");

        kuXuanLight.close();

        System.out.println("----关灯完成---");
    }

}

public class Test {
    public static void main(String[] args) {
        Boss boss = new Boss("奶茶店老板");

        //天黑开灯,一定要开,因为不是黑店
        boss.openLight();
        //下班关灯,一定要关,因为不能浪费电,做新时代的好青年
        boss.closeLight();
    }
}

上面就是我作为老板每天一定要做的事情。设计模式MM看到后,很是心疼我啊,说我一天这么累,还要亲自去开灯和关灯(我的职责太多了),并且我还要有新的灯安装的话,我还有修改代码(违反开闭原则)。我和灯之间的耦合太严重了。这样问题比较多。
于是建议我去看一下“命令模式”,让我和灯之间解耦,能否通过遥控器来控制灯的打开和关闭,并且这个遥控器还可以控制电视机的打开和关闭,空调的打开和关闭,让我可以专心做一个Boss,我只管发命令。

故事主角—命令模式

命令模式:将一个“请求”封装为一个对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。(动作Aciton模式)或(事务Transaction模式)

命令模式可以让请求发送者和接受者完全解耦,发送者与接受者之间没有直接的引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成执行请求动作。

命令模式类关系图

在命令模式中包含如下几个角色:

  • Command(抽象命令对象):抽象命令一般是一个接口或者一个抽象类,在其中声明了执行请求的execute()等方法,通过这些方法调用请求接受者的相关操作。
  • ConcreteCommand(具体命令类):具体命令是抽象命令类的子类,实现了execute方法。
  • Invoker(调用者):调用者也就是请求的发送者,通过执行命令对象来执行请求。
  • Receiver(接收着):接收者执行与请求相关的操作,实现对请求的业务处理。

简单用代码描述:

public interface Command {
    void execute();
}

public class ConcreteCommand implements Command {
    private Receiver receiver;

    public ConcreteCommand(Receiver receiver){
        this.receiver = receiver;
    }
    @Override
    public void execute() {
        receiver.action();
    }
}

public class Receiver {

    public void action(){
        //业务处理
    }
}

public class Invoker {
    private Command command;

    //构造注入 
    public Invoker(Command command){
        this.command = command;
    }

    //设值注入  
    public void setCommand(Command command) {  
        this.command = command;  
    }  

    public void doString(){
        command.execute();
    }
}


命令模式的本质是对请求的封装,一个请求对应于一个命令(比如一个开灯请求,对应一个开灯的命令),将发出命令的责任和执行命令的责任分开!
从上面看,命令模式是根据抽象命令编程,只有实现了抽象命令类的具体命令才能与请求接收者关联。

武功修炼

通过对象命令模式的简单学习,改造之前设计的开灯方式,现在使用一个遥控器,让遥控器帮Boss完成任务,Boss只需要下达命令即可,并且能够实现返回上一次执行的操作,即撤销功能。

//抽象接口命令
public interface Command {
    //执行操作
    void execute();
    //撤销操作
    void undo();
}
//具体命令对象
public class LightOffCommand implements Command{

     private Light light;
    //通过set设值
    public void setLight(Light light){
        this.light = light;
    }
    @Override
    public void execute() {
        light.open();
    }

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

//具体命令对象
public class LightOnCommand implements Command {
   private Light light;
    //通过set设值
    public void setLight(Light light){
        this.light = light;
    }
    @Override
    public void execute() {
        light.open();
    }

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

public interface Light {

     void open();//打开

     void close();//关闭

}
//请求实际的执行者 Receiver
public class KuXuanLight implements Light{
    private String name = "酷炫的灯";

    @Override
    public void open(){
        System.out.println("---打开--" + this.name );
    }

    @Override
    public void close(){
        System.out.println("---关闭--" + this.name );
    }
}

//相当于Invoker ,这个遥控器是语音智能的遥控器
public class RemoteControl {
    private Command onCommand;
    private Command offCommand;
    private Command onDoCommand;



    public RemoteControl(Command onCommand,Command offCommand){
        this.onCommand = onCommand;
        this.offCommand = offCommand;
    }

    public void onButtonPress(){
        onDoCommand = onCommand;
        onCommand.execute();
    }

    public void offButtonPress(){
        onDoCommand = offCommand;
        offCommand.execute();
    }

    public void onDoButtonPress(){
        onDoCommand.undo();
    }

}

//老板
public class Boss {

    private String name;
    //这是一个智能的遥控器,老板可以通过say的方式发起命令
    private RemoteControl remoteControl;
    public Boss(String name){
        super();
        this.name = name;
    }

    public void setRemoteControl(RemoteControl remoteControl){
        this.remoteControl = remoteControl;
    }

    public void sayOpenLight(){
        System.out.println("----晚上了,天黑了,准备开灯---");
        remoteControl.onButtonPress();

        System.out.println("----开灯完成---");
    }

    public void sayCloseLight(){
        System.out.println("----下班了,准备关灯---");

        remoteControl.offButtonPress();

        System.out.println("----关灯完成---");
    }

    public void sayReturnLight(){
        System.out.println("----刚才操作错误,撤销操作---");

        remoteControl.onDoButtonPress();

        System.out.println("----撤销完成---");
    }
}


public class Test {
    public static void main(String[] args) {
        Light light = new KuXuanLight();

        LightOnCommand onCommand = new LightOnCommand();
        onCommand.setLight(light);

        LightOffCommand offCommand = new LightOffCommand();
        offCommand.setLight(light);

        Boss boss = new Boss("专门下达命令的奶茶店老板");
        RemoteControl remoteControl = new RemoteControl(onCommand,offCommand);

        boss.setRemoteControl(remoteControl);

        //天黑开灯,一定要开,因为不是黑店
        boss.sayOpenLight();
        //天还不太黑,还是先关了吧,可以使用撤销进行操作(这里是演示撤销功能)
        boss.sayReturnLight();

        System.out.println("================华丽的分割线================");

        //天黑开灯,一定要开,因为不是黑店
        boss.sayOpenLight();
        //下班关灯,一定要关,因为不能浪费电,做新时代的好青年
        boss.sayCloseLight();
    }
}


----晚上了,天黑了,准备开灯---
---打开--酷炫的灯
----开灯完成---
----刚才操作错误,撤销操作---
---关闭--酷炫的灯
----撤销完成---
================华丽的分割线================
----晚上了,天黑了,准备开灯---
---打开--酷炫的灯
----开灯完成---
----下班了,准备关灯---
---关闭--酷炫的灯
----关灯完成---

此时请求者和执行者完全解耦,老板只需要通过语音发起命令,然后不用管正在的执行者是谁。
比如有一天设计模式MM偷偷把KuXuanLight,换成KuXuanDiaoBoLight,Boss的我也不用管,依旧发起命令即可!

public class KuXuanDiaoBoLight implements Light {
    private String name = "酷炫碉堡了的五彩斑斓的灯";

    @Override
    public void open(){
        System.out.println("---打开--" + this.name );
    }

    @Override
    public void close(){
        System.out.println("---关闭--" + this.name );
    }
}

//只要改下下面这一行即可
Light light = new KuXuanDiaoBoLight();

目前是这个智能遥控器智能控制一个灯,如果想控制多个功能开关的话,只需要将遥控器里的命令改为如下格式:

private Command onCommand;
private Command offCommand;

public RemoteControl(Command onCommand,Command offCommand){
        this.onCommand = onCommand;
        this.offCommand = offCommand;
}

// --->>>改为--->>>>


private Command[] onCommand;
private Command[] offCommand;
int initSolt = 1;//遥控器有功能位
public RemoteControl(){
        onCommand = new Command[initSolt];
        offCommand = new Command[initSolt];
}
public eetRemoteControl(int slot,Command onCommand,Command offCommand){
        onCommand[slot] = onCommand;
        this.offCommand[slot] = offCommand;
}

//撤销可以使用Stack ,多次撤销操作-这里不进行说明了

武功深入

通过对命令的修炼,发现命令还有一些其他的功能,如可以使用宏命令,说简单一点,就是可以发起一次请求可以执行发个操作!
比如奶茶店的遥控器有个功能是“Party”模式,那么按了这个按钮就会进行
- 打开彩灯
- 打开音响,播放音乐
- 奶茶店喷雾

那么这一系列的操作可以放到一个宏命令中进行执行

public class MacroCommand implements Command{

    Command[] partyOn = {"开彩灯命令","开音响命令","开喷雾命令"};
    Command[] partyOn = {"","",""};//对应的关闭命令
    //这里使用简写,实际上可以使用set方式设置
}

这样就完成了一个宏命令的执行过程!

还有命名模式可以实现队列请求;

命令模式 - 队列请求

工作的队列和计算的线程直接完全是解耦的,比如这个线程这一刻在处理财务运算,下一刻可能就处理读取网络数据了。

还有命名模式可以实现请求日志,请求日志可以看:
6 请求日志

故事结局

命令模式是一种使用频率非常高的设计模式,它可以将请求发送者与接收者解耦,请求发送者通过命令对象来间接引用请求接收者,使得系统具有更好的灵活性和可扩展性。

优点

  • 降低系统的耦合度。由于请求者与接收者之间不存在直接引用,因此请求者与接收者之间实现完全解耦,相同的请求者可以对应不同的接收者,同样,相同的接收者也可以供不同的请求者使用,两者之间具有良好的独立性。

  • 新的命令可以很容易地加入到系统中。由于增加新的具体命令类不会影响到其他类,因此增加新的具体命令类很容易,无须修改原有系统源代码,甚至客户类代码,满足“开闭原则”的要求。

  • 可以比较容易地设计一个命令队列或宏命令(组合命令)。

  • 为请求的撤销(Undo)和恢复(Redo)操作提供了一种设计和实现方案。

缺点

  • 使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个对请求接收者的调用操作都需要设计一个具体命令类,因此在某些系统中可能需要提供大量的具体命令类,这将影响命令模式的使用。

适用场景

在以下情况下可以考虑使用命令模式:

  • 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。请求调用者无须知道接收者的存在,也无须知道接收者是谁,接收者也无须关心何时被调用。

  • 系统需要在不同的时间指定请求、将请求排队和执行请求。一个命令对象和请求的初始调用者可以有不同的生命期,换言之,最初的请求发出者可能已经不在了,而命令对象本身仍然是活动的,可以通过该命令对象去调用请求接收者,而无须关心请求调用者的存在性,可以通过请求日志文件等机制来具体实现。

  • 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。

  • 系统需要将一组操作组合在一起形成宏命令。

上面总结全部来源:8 命令模式总结

Next 期待下一篇吧!完成了奶茶店奶茶酷炫灯的控制,顾客更多了,有不同国家的,奶茶已经无法满足顾客需求,我决定加中国龙井茶和叶门的摩卡咖啡!下一篇:模板方法模式,制作更多好喝的饮品!

参考


如果您觉得这篇博文对你有帮助,请点赞或者喜欢,让更多的人看到,谢谢!

如果帅气(美丽)、睿智(聪颖),和我一样简单善良的你看到本篇博文中存在问题,请指出,我虚心接受你让我成长的批评,谢谢阅读!
祝你今天开心愉快!


欢迎访问我的csdn博客,我们一同成长!

不管做什么,只要坚持下去就会看到不一样!在路上,不卑不亢!

博客首页 : http://blog.csdn.net/u010648555

猜你喜欢

转载自blog.csdn.net/u010648555/article/details/80710762
今日推荐