行为篇-命令模式

Posted zhixuChen333

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了行为篇-命令模式相关的知识,希望对你有一定的参考价值。

文章目录


前言

命令是一个对象向另一个或多个对象发送的指令信息。命令的发送方负责下达指令,接收方则根据命令触发相应的行为。作为一种数据(指令信息)驱动的行为型设计模式,命令模式(Command)能够将指令信息封装成一个对象,并将此对象作为参数发送给接收方去执行,以使命令的请求方与执行方解耦,双方只通过传递各种命令过象来完成任务。此外,命令模式还支持命令的批量执行、顺序执行以及命令的反执行等操作。


提示:以下是本篇文章正文内容,下面案例可供参考

一、对电灯的控制

现实生活中,命令模式随处可见,如遥控器对电视机发出的换台、调音量等指令;将军针对士兵执行进攻、撤退或者先退再进的任务所下达的一系列命令;餐厅中顾客为了让厨师按照自己的需求烹饪所需的菜品,需要与服务员确定的点菜单。除此之外,在进行数据库的增、删、改、查时,用户会向数据库发送SQL语句来执行相关操作,或提交回滚操作,这也与命令模式非常类似。

1.灯泡类

我们先从一个简单的电灯控制系统入手,如代码所示。其中开关可被视为命令的发送(请求)方,而灯泡则对应为命令的执行方。我们先从命令执行方开始代码实战。灯泡类一定有这样2个行为:通电灯亮,断电灯灭,

public class Bulb 
    public void on()
        System.out.println("灯亮。");
    

    public void off()
        System.out.println("灯灭。");
    

2.开关类

public class Switcher 
    private Bulb bulb;

    public Switcher(Bulb bulb) 
        this.bulb = bulb;
    

    //按钮触发事件
    public void buttonPush()
        System.out.println("按下按钮......");
        bulb.on();
    

    public void buttonPop()
        System.out.println("弹起按钮......");
        bulb.off();
    

3.客户端类

public class Client 
    public static void main(String[] args) 
        Switcher switcher = new Switcher(new Bulb());
        switcher.buttonPush();
        switcher.buttonPop();
    

输出结果:
按下按钮......
灯亮。
弹起按钮......
灯灭。

问题:

  1. 客户端类Client在灯泡接入开关,依次对其进行了按钮操作,作为结果,我们可以看到代码中触发的灯亮与灯灭行为。虽然电灯一切工作正常,但是需要特别注意的是,我们声明了灯泡类的引用,并于构造方法中将其初始化,那么无疑这里的开关与灯泡就绑定了,也就是说开关与灯泡强耦合了。

二、 开关命令

既然是命令模式,那么一定要从“命令”本身切入。此前我们已经实现了命令的请求方(开关类)与执行方(灯泡类)两个模块,要解决它们之间的耦合问题,我们决定引入命令模块。

1.命令接口

不管是什么命令,它一定是可以被执行的,所以我们首先定义一个命令接口,以确立命令的执行规范。

public interface Command 
    //执行命令
    void exe();

    //反向执行命令
    void unexe();

2.开关命令类

public class SwitchCommand implements Command

    private Bulb bulb;

    public SwitchCommand(Bulb bulb) 
        this.bulb = bulb;
    

    @Override
    public void exe() 
        bulb.on();//执行开灯操作
    

    @Override
    public void unexe() 
       bulb.off();//执行关灯操作
    

注意:

  1. 对于代码中的开关命令类SwitchCommand,由于场景比较简单,我们将所有命令简化为一个类来实现了。其实更确切的做法是将每个命令封装为一个类,也就是可以进一步将其拆分为“开命令”(OnCommand)与“关命令”(OffCommand)两个实现类,其中前者的执行方法中触发灯泡的开灯操作,反向执行方法中则触发灯泡的关灯操作,而后者则反之,以此支持更多高级功能。
  2. 这时作为命令请求方的开关就彻底与灯泡解耦了,也就是说,开关不能直接控制电灯了。

3.开关类

public class Switcher 
    private Command command;

    //设置命令
    public void setCommand(Command command) 
        this.command = command;
    

    //按钮事件绑定
    public void buttonPush()
        System.out.println("按下按钮......");
        command.exe();
    

    public void buttonPop()
        System.out.println("弹起按钮......");
        command.unexe();
    

说明:

  1. 开关类Switcher不再引入任何灯泡对象,取而代之的是持有的命令接口Command,并提供了命令设置方法setCommand(),以实现命令的任意设置。

4.客户端类

public class Client 
    public static void main(String[] args) 
        Switcher switcher = new Switcher();//命令请求方
        Bulb bulb = new Bulb();//命令执行方
        Command switchCommand = new SwitchCommand(bulb);//开关命令

        switcher.setCommand(switchCommand);
        switcher.buttonPush();
        switcher.buttonPop();
    

输出结果:
按下按钮......
灯亮。
弹起按钮......
灯灭。

说明:

  1. 客户端类Client首先实例化了开关switcher(命令请求方),然后实例化了灯泡bulb(命令执行方),最后实例化了一个开关命令switchCommand并注入灯泡(灯泡对应的开关命令),这样三方模块就全部构建完成了。
  2. 有些读者可能会产生这样的疑问:我们加入命令模块是为了将命令请求方与命令执行方解耦,而我们的应用场景只是一个简单的电灯开关控制系统,何必如此大动干戈?此时看来的确没有太大的意义,但在命令模式的架构下我们就可以为系统添加一些高级功能了。

三、 霓虹灯闪烁

电灯控制系统虽然已搭建完成,但此时实现的只是灯泡的开关功能,不能完全满足用户的需求,例如用户要求实现灯泡闪烁的霓虹灯效果。当下仅有的开关命令是无法实现这种效果的。

1.闪烁命令类

要实现这种一键自动完成的功能,我们得添加新的“闪烁”命令类。

public class FlashCommand implements Command 
    private          Bulb    bulb;
    private volatile boolean neoRun = false;//闪烁命令运行状态

    public FlashCommand(Bulb bulb) 
        this.bulb = bulb;
    

    @Override
    public void exe() 
        if (!neoRun) //非闪烁命令运行是才能启动闪烁线程
            neoRun = true;
            System.out.println("霓虹灯闪烁任务启动");
        
        new Thread(() -> 
            try 
                while (neoRun) 
                    bulb.on();//开灯
                    Thread.sleep(500);
                    bulb.off();//关灯
                    Thread.sleep(500);
                
             catch (InterruptedException e) 
                e.printStackTrace();
            
        ).start();
    

    @Override
    public void unexe() 
        neoRun = false;
        System.out.println("霓虹灯闪烁任务结束");
    

说明:

  1. 此处的代码逻辑不是重点,读者更需要关注的是这个闪烁命令同样符合命令接口Command的标准,如此才能保证良好的系统兼容性,并成功植入开关控制器芯片(命令请求方)完成事件与命令的绑定。

2.客户端类

public class Client 
    public static void main(String[] args) throws InterruptedException 
        Switcher switcher = new Switcher();//命令请求方
        Bulb bulb = new Bulb();//命令执行方
        Command switchCommand = new FlashCommand(bulb);//开关命令

        switcher.setCommand(switchCommand);
        switcher.buttonPush();
        Thread.sleep(3000);
        switcher.buttonPop();
    

输出结果:
按下按钮......
霓虹灯闪烁任务启动
灯亮。
灯灭。
灯亮。
灯灭。
灯亮。
灯灭。
弹起按钮......
霓虹灯闪烁任务结束

说明:

  1. 客户端对霓虹灯闪烁效果非常满意,达到了预期的效果。可以看到,在命令模式构架的电灯开关控制系统中,我们只是新添加了一个闪烁命令,并没有更改任何模块便使灯泡做出了不同的行为响应。也就是说,命令模式能使我们在不改变任何现有系统代码的情况下,实现命令功能的无限扩展。

四、物联网

通过对电灯开关控制系统的例子可以看到,命令模式对命令的抽象与封装能让控制器(命令请求方)与电器设备(命令执行方)彻底解耦。命令的多态带来了很大的灵活性,我们可以将任何命令绑定到任何控制器上。

例如在物联网或是智能家居场景中,发出命令请求的控制器端可能有键盘、遥控器,甚至是手机App等,而作为命令执行方的电器设备端可能有灯泡、电视机、收音机、空调等。

1.电视机类

我们可以使用命令模式,忽略繁杂的电器设备接口,实现任意设备间的端到端控制。例如用户需要用键盘同时控制电视机和电灯,我们首先来定义命令执行方的电视机类。

public class TV 
    public void on()
        System.out.println("电视机开启");
    

    public void off()
        System.out.println("电视机关闭");
    

    public void channelUp()
        System.out.println("电视机频道+");
    

    public void channelDown()
        System.out.println("电视机频道-");
    

    public void volumeUp()
        System.out.println("电视机音量+");
    

    public void volumeDown()
        System.out.println("电视机音量-");
    

说明:

  1. 电视机类比电灯类的功能复杂得多,除了开关还有频道转换及音量调节等功能。

2.电视命令类

//电视开机命令类
public class TVOnCommand implements Command 
    private TV tv;

    public TVOnCommand(TV tv) 
        this.tv = tv;
    

    @Override
    public void exe() 
        tv.on();
    

    @Override
    public void unexe() 
        tv.off();
    


//电视关机命令类
public class TVOffCommand implements Command 
    private TV tv;

    public TVOffCommand(TV tv) 
        this.tv = tv;
    

    @Override
    public void exe() 
        tv.off();
    

    @Override
    public void unexe() 
        tv.on();
    


//电视频道上调命令类
public class TVChannelUpCommand implements Command 
    private TV tv;

    public TVChannelUpCommand(TV tv) 
        this.tv = tv;
    

    @Override
    public void exe() 
        tv.channelUp();
    

    @Override
    public void unexe() 
        tv.channelDown();
    


说明:

  1. 电视机对应的一系列命令定义完毕,其他命令大同小异,请读者自行实现。

3.键盘控制器类Keyboard

public class Keyboard 
    public enum KeyCode 
        F1, F2, ESC, UP, DOWN, LEFT, RIGHT;
    

    private Map<KeyCode, List<Command>> keyCommands = new HashMap<>();

    //按键与命令映射
    public void bindKeyCommand(KeyCode keyCode, List<Command> commands) 
        this.keyCommands.put(keyCode, commands);
    

    //触发按键
    public void onKeyPressed(KeyCode keyCode) 
        System.out.println(keyCode + "键按下......");
        List<Command> commands = this.keyCommands.get(keyCode);
        if (commands == null) 
            System.out.println("警告,无效命令");
            return;
        
        commands.stream().forEach(command -> command.exe());
    

说明:

  1. 键盘控制器类Keyboard在第2行定义的枚举类型KeyCode对应键盘上的所有键,此处我们暂且定义7个键。
  2. bindKeyCommand()方法提供了“按键与命令映射”的键盘命令自定义功能,如此一来用户就可以将任意命令映射至键盘的任意按键上了。
  3. 提供给外部按键事件的触发方法,它会以传入的KeyCode枚举从按键命令映射keyCommands中获取对应的命令集,并依次执行。

4.客户端类

public class Client 
    public static void main(String[] args) 
        Keyboard keyboard = new Keyboard();
        TV tv = new TV();
        Command tvOnCommand = new TVOnCommand(tv);
        Command tvOffCommand = new TVOffCommand(tv);
        Command tvChannelUpCommand = new TVChannelUpCommand(tv);

        //按键与命令映射
        keyboard.bindKeyCommand(Keyboard.KeyCode.F1, Arrays.asList(tvOnCommand));
        keyboard.bindKeyCommand(Keyboard.KeyCode.LEFT, Arrays.asList(tvChannelUpCommand,tvChannelUpCommand,tvChannelUpCommand));
        keyboard.bindKeyCommand(Keyboard.KeyCode.ESC, Arrays.asList(tvOffCommand));

        //触发按键
        keyboard.onKeyPressed(Keyboard.KeyCode.F1);
        keyboard.onKeyPressed(Keyboard.KeyCode.LEFT);
        keyboard.onKeyPressed(Keyboard.KeyCode.UP);
        keyboard.onKeyPressed(Keyboard.KeyCode.ESC);
    

输出结果:
F1键按下......
电视机开启
LEFT键按下......
电视机频道+
电视机频道+
电视机频道+
UP键按下......
警告,无效命令
ESC键按下......
电视机关闭

说明:

  1. 我们的系统框架已经搭建得非常完善了,读者可以自行定义开灯命令,再将其植入按键命令映射。
  2. 此外,我们还可以加入操作记录功能,或者更高级的反向执行撤销、事务回滚等功能,读者可以自行实践代码。

总结

提示:这里对文章进行总结:`

  1. 命令模式的应用使我们的各种设备都连接了起来,要给电器设备(命令执行方)发送命令时,只需要扩展新的命令并映射至键盘(命令请求方或发送方)的某个按键(方法)。命令模式巧妙地利用了命令接口将命令请求方与命令执行方隔离开来,使发号施令者与任务执行者解耦,甚至意识不到对方接口的存在而全靠命令的上传下达。
  2. 命令模式的各角色定义如下:
  • Command(命令接口):定义命令执行的接口标准,可包括执行与反向执行操作。
  • ConcreteCommand(命令实现):命令接口的实现类,可以有任意多个,其执行方法中调用命令执行方所对应的执行方法。对应本章例程中的各种命令类,如开灯命令类BulbOnCommand、电视机频道上调命令类TVChannelUpCommand等。
  • Receiver(命令执行方):最终的命令执行方,对应本章例程中的各种电器设备,如灯泡类Bulb、电视机类TV。

以上是关于行为篇-命令模式的主要内容,如果未能解决你的问题,请参考以下文章

命令模式

命令模式

设计模式--命令模式

从零开始学习Java设计模式 | 行为型模式篇:命令模式

从零开始学习Java设计模式 | 行为型模式篇:命令模式

设计模式之命令模式-使用命令模式实现遥控器及总结