美文网首页
浅谈设计模式7——命令模式

浅谈设计模式7——命令模式

作者: 吃根香蕉压压惊 | 来源:发表于2016-05-13 12:46 被阅读33次


不知道是不是因为看书状态不好,书上的例子确实总是有种云里雾里的感觉,不过幸好有程序,看程序基本上就明白这个所谓的命令模式大概是怎么回事了。

1、背景
为了简化说明,书中的例子将在此处简化。现在个遥控器,共有14个按钮,左边7个表示“开”,右边7个表示“关”。这样,每一对开关可以对应一种家用电器。当按下开的时候,对应的家用电器启动。当按下关的时候,家用电器关闭。
如果到这来说,其实就很简单。可以将每对按钮设定为一个类,类中存有开启和关闭某个家用电器的操作。用一个集合对象来存储这7组按钮即可。但是,问题来了。如果原来风扇扔掉了,换成了空调——也就是控制的家用电器的种类发生了变化,或者说有一些家电不用了,或者更极端些,希望一组按钮能开启、关闭所有家电——总之,遥控器的功能要发生改变,怎么办?

首先确认的是,为了保证更好的封装特性,不能修改遥控器类。那么据此,我们引入当前的模式:命令模式。

2、命令模式
命令模式将:发出命令的控制实体(遥控器上的按钮),接受命令的实体(遥控器上发出的不同的红外线光),执行命令的功能实体(家用电器)三者进行剥离。
接受命令的实体相当于遥控器与家电的一个通用接口,或者说是一个管道。管道一头连接着发出命令的实体,为控制实体提供一个统一的发出命令的接口。管道另一头连接着执行命令的功能实体,为功能实体提供一个统一接收命令的接口。命令在这个管道上流通。

好吧,这么说还是很抽象,我觉得上程序应该更能有所体会。

public class Test2 {
    public static void main(String argv[])
    {
        Light light=new Light();//定义电灯
        Lighton lighton=new Lighton(light);//定义关于电灯开操作的接收实体
        Lightoff lightoff=new Lightoff(light);//定义关于电灯关操作的接收实体
        
        CeilingFan ceilingFan=new CeilingFan();
        Ceilingfanon ceilingfanon=new Ceilingfanon(ceilingFan);
        Ceilingfanoff ceilingfanoff=new Ceilingfanoff(ceilingFan);
        
        CtlCommand con=new CtlCommand(2); //定义控制实体
        con.setExecutors(0, lighton, lightoff); //将接收实体加入控制实体中
        con.setExecutors(1, ceilingfanon, ceilingfanoff);
        
        con.pushOnButton(0);//控制实体,通过接受实体,调用执行实体的功能
        con.pushOnButton(1);
        
        con.pushOffButton(0);
        con.pushOffButton(1);
    }
    
}

//发出命令的控制实体
class CtlCommand
{
    private Executor[] onExecutors;
    private Executor[] offExecutors;
    
    public CtlCommand(int buttonAmount) //给出按钮
    {
        this.onExecutors=new Executor[buttonAmount]; 
        this.offExecutors=new Executor[buttonAmount];
        
        for (int i = 0; i < offExecutors.length; i++) {
            this.onExecutors[i]=new Noexecute();//Noexecute为不会产生任何功能的功能实体。可以看作是虚拟的家用电器。
            this.offExecutors[i]=new Noexecute();
        }
    }
    
    public void setExecutors(int buttonNumber, Executor onExecutor, Executor offExecutor) {//给每个按钮配置接收实体
        this.onExecutors[buttonNumber]=onExecutor;
        this.offExecutors[buttonNumber]=offExecutor;
    }
    
    public void pushOnButton(int buttonNumber){//接收实体提供统一的execute()接口,调用功能实体的功能
        this.onExecutors[buttonNumber].execute();
    }
    public void pushOffButton(int buttonNumber){
        this.offExecutors[buttonNumber].execute();
    }
}



//定义了2个家用电器
class Light//灯
{
    public void on(){}
    public void off(){}
}



class CeilingFan//吊扇
{
    //风速
    public void low(){}
    public void medium(){}
    public void high(){}
    
    //开关
    public void on(){}
    public void off(){}
}

//定义接受命令实体的统一接口
interface Executor
{
    public void execute();
}

//电灯接收实体
class Lighton implements Executor
{
    Light light;
    public Lighton(Light light) {this.light=light;}
    public void execute() {this.light.on();}
}

class Lightoff implements Executor
{
    Light  light;
    public Lightoff(Light light) {this.light=light;}
    @Override
    public void execute() {this.light.off();}
}

//电扇接受实体
class Ceilingfanon implements Executor
{
    CeilingFan ceilingFan;
    public Ceilingfanon(CeilingFan ceilingFan) {
        this.ceilingFan=ceilingFan;
    }
    @Override
    public void execute() {
        this.ceilingFan.on();
        this.ceilingFan.medium();       
    }
}

class Ceilingfanoff implements Executor
{
    CeilingFan ceilingFan;
    public Ceilingfanoff(CeilingFan ceilingFan) {
        this.ceilingFan=ceilingFan;
    }
    @Override
    public void execute() {
        this.ceilingFan.off();
    }
}
//空接收命令实体
class Noexecute implements Executor
{
    @Override
    public void execute() {}
}

不得不说,这个模式确实看起来很复杂,一方面涉及到的类和接口比较多。另一方面,每个类或者接口之间的关系也比较复杂。但是复杂并不代表一点规律都没有。下面将详细说明这些关系,以及其中蕴含的规律:

3、代码解析
(1)程序共有两个功能实体:Light和CeilingFan(此处简称灯和吊扇)。其中灯实体只有on()和off()方法,因此恰可以对应遥控器按钮的buttonOn和buttonOff。而吊扇实体中,由于还可以调节风速,因此规定,对于吊扇的buttonOn来说,不仅要把吊扇打开,还需要指定风速为medium()。吊扇的buttonOff则对应off()函数。

(2)由于一个按钮对应一个(或一组)操作,因此需要将每个操作,再次封装到一个类中。这个类就是接收实体。其使用了统一的Executor接口。真实由于这个原因,所以出现了LightOn类和LightOff类,CeilingFanon类和CeilingFanoff类。这四个类都实现了execute()方法,从而为控制实体提供了统一的调用接口。而具体的操作,则通过execute()方法进行封装。
同时,还需注意到,这些接收实体以组合方式,将功能实体包含进来。这也是第一步的解耦过程。因为同一类型的吊扇,可能又有很多个品牌。采用组合的方式,极大地放宽了吊扇的品种。

(3)而控制实体相当于一个大容器。他将所有的Executor接口对应的类型都聚集到这里,然后再用Executor提供的统一方法,对某个函数进行调用。这是第二步的解耦。控制实体只是判断方法——控制实体是方法的函数(此处用到了数学概念,数学思维确实真的很好用,尤其是分析问题的时候),而不是方法对应的某个对象的函数(即控制实体是execute()的函数,而不是execute()所属对象的函数)。

这样,便将控制实体、接受实体、功能实体进行了剥离。而每个实体,又有一点点联系——即他们的联系全都集中在execute()这个方法中。

功能实体为接受实体提供了execute()的内容。而接收实体为控制实体提供了execute()这张皮。——突然想到了月饼:
功能实体就好像馅料,接收实体就好像月饼皮,控制实体就好像食客。

4、继续工作
其实这个事还是没有说完,书上又进一步做了更深入的讨论:
1)遥控器上又提供了一个undo()撤回按钮。当按这个按钮的时候,上一个操作将被撤回。比如上一个操作为:开灯。那按一下undo()按钮,就能实现关灯的操作。
这个其实也很好做。还是看代码:

public class Test2 {
    public static void main(String argv[])
    {
        Light light=new Light();//定义电灯
        Lighton lighton=new Lighton(light);//定义关于电灯开操作的接收实体
        Lightoff lightoff=new Lightoff(light);//定义关于电灯关操作的接收实体
        
        CeilingFan ceilingFan=new CeilingFan();
        Ceilingfanon ceilingfanon=new Ceilingfanon(ceilingFan);
        Ceilingfanoff ceilingfanoff=new Ceilingfanoff(ceilingFan);
        
        CtlCommand con=new CtlCommand(2); //定义控制实体
        con.setExecutors(0, lighton, lightoff); //将接收实体加入控制实体中
        con.setExecutors(1, ceilingfanon, ceilingfanoff);
        
        con.pushOnButton(0);//控制实体,通过接受实体,调用执行实体的功能
        con.pushOnButton(1);
        
        con.pushOffButton(0);
        con.pushOffButton(1);
    }
    
}

//发出命令的控制实体
class CtlCommand
{
    private Executor[] onExecutors;
    private Executor[] offExecutors;
    private Executor undoExecutor;//增加一个记录上次执行操作的属性
    
    public CtlCommand(int buttonAmount) //给出按钮
    {
        this.onExecutors=new Executor[buttonAmount]; 
        this.offExecutors=new Executor[buttonAmount];
        
        this.undoExecutor=new Noexecute();//初始化一个undo
        
        for (int i = 0; i < offExecutors.length; i++) {
            this.onExecutors[i]=new Noexecute();//Noexecute为不会产生任何功能的功能实体。可以看作是虚拟的家用电器。
            this.offExecutors[i]=new Noexecute();
        }
    }
    
    public void setExecutors(int buttonNumber, Executor onExecutor, Executor offExecutor) {//给每个按钮配置接收实体
        this.onExecutors[buttonNumber]=onExecutor;
        this.offExecutors[buttonNumber]=offExecutor;
    }
    
    public void pushOnButton(int buttonNumber){//接收实体提供统一的execute()接口,调用功能实体的功能
        this.onExecutors[buttonNumber].execute();
        this.undoExecutor=this.onExecutors[buttonNumber];
    }
    public void pushOffButton(int buttonNumber){
        this.offExecutors[buttonNumber].execute();
        this.undoExecutor=this.offExecutors[buttonNumber];
    }
    
    public void pushUndoButton()//点击撤销按钮
    {
        this.undoExecutor.undo();
    }
    
}



//定义了2个家用电器
class Light//灯
{
    public void on(){}
    public void off(){}
}



class CeilingFan//吊扇
{
    //风速
    public void low(){}
    public void medium(){}
    public void high(){}
    
    //开关
    public void on(){}
    public void off(){}
}

//定义接受命令实体的统一接口
interface Executor
{
    public void execute();
    public void undo();//提供撤销操作接口
}

//电灯接收实体
class Lighton implements Executor
{
    Light light;
    public Lighton(Light light) {this.light=light;}
    public void execute() {this.light.on();}
    public void undo(){this.light.off();}
}

class Lightoff implements Executor
{
    Light  light;
    public Lightoff(Light light) {this.light=light;}
    @Override
    public void execute() {this.light.off();}
    public void undo(){this.light.on();}
}

//电扇接受实体
class Ceilingfanon implements Executor
{
    CeilingFan ceilingFan;
    public Ceilingfanon(CeilingFan ceilingFan) {
        this.ceilingFan=ceilingFan;
    }
    @Override
    public void execute() {
        this.ceilingFan.on();
        this.ceilingFan.medium();       
    }
    public void undo(){this.ceilingFan.off();}
}

class Ceilingfanoff implements Executor
{
    CeilingFan ceilingFan;
    public Ceilingfanoff(CeilingFan ceilingFan) {
        this.ceilingFan=ceilingFan;
    }
    @Override
    public void execute() {
        this.ceilingFan.off();
    }
    public void undo(){this.ceilingFan.off();}
}
//空接收命令实体
class Noexecute implements Executor
{
    @Override
    public void execute() {}
    public void undo(){}
}

这个就不详细解析了。大家注意下四个位置(两个实体,一个接口)。
第一个位置:接口Executor中增加了undo接口。
第二个位置:所以,实现该接口的所有类,都给出了相对于execute()相反操作的undo()方法。
第三个位置:控制实体增加了一个属性,该属性记录上次操作对应的接收实体。
第四个位置:增加了按钮对应的方法pushUndoButton()。

2)对于像吊扇这种有风速控制的家电,是否可以根据其前一个状态恢复?
3)是否一个按钮可以控制一堆家用电器,比如同时开启电扇、电灯、电视这三个家用电器?
4)是否可以记录最后若干个操作,当多次按undo()按钮时,可以顺次进行操作的回退操作。比如操作序列:开电视、开灯、开电扇、开洗衣机。则回退操作序列为:关洗衣机、关电扇、关灯、关电视?

此处就不仔细说了。架构已经给出。:-P

相关文章

  • 浅谈设计模式7——命令模式

    序不知道是不是因为看书状态不好,书上的例子确实总是有种云里雾里的感觉,不过幸好有程序,看程序基本上就明白这个所谓的...

  • 设计模式7:命令模式

    之前学习过的设计模式,有对行为的封装(策略模式),有对创建的封装(工厂方法和抽象工厂),今天要学的命令模式(Com...

  • 设计模式-命令模式

    命令模式 介绍: 模式模式(Command Pattern),是行为型设计模式之一。命令模式相对于其他的设计模式来...

  • 命令模式(Command)

    本文参考自:《JAVA设计模式》之命令模式(Command) 1. 作用 命令模式属于对象的行为模式。命令模式又称...

  • 文章收集

    java设计模式之命令模式

  • 浅谈Java设计模式之命令模式

    命令模式可以将请求发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发...

  • 设计模式详解——命令模式

    本篇文章介绍一种设计模式——命令模式。本篇文章内容参考《JAVA与模式》之命令模式。 一、命令模式的概念 命令模式...

  • 7.设计模式(命令模式)

    1.命令模式最常见的应用场景:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什...

  • JavaScript设计模式七(命令模式)

    JavaScript设计模式七(命令模式) 定义: 命令模式是最简单和优雅的模式之一,命令模式中的命令指的是一个执...

  • 设计模式之命令模式

    @(架构之路之设计模式代码) 设计模式之命令模式 欢迎关注作者简书csdn传送门 [TOC] 介绍   命令模式属...

网友评论

      本文标题:浅谈设计模式7——命令模式

      本文链接:https://www.haomeiwen.com/subject/tbfprttx.html