浅谈设计模式 - 命令模式(七)

简介: 浅谈设计模式 - 命令模式(七)

前言:



命令模式也是一种比较常见的行为型模式,可以想象我们的手机智能遥控器,通过按动按钮的形式开启各种家具,说白了,就是将一系列的请求命令封装起来,不直接调用真正执行者的方法,这样比较好扩展。需要注意的是命令模式和策略模式相似,所以有时候可能容易弄混,这篇文章将会详细介绍命令模式


文章目的:


  1. 了解命令的模式的特点
  2. 简单对比命令模式和策略模式
  3. 命令模式的优缺点总结


什么是命令模式?


解释:把“请求”封装为对应的对象,使用不同的请求参数化对象,命令模式支持撤销撤销的操作

命令模式是一种行为型模式,实现了接口调用对象和返回对象,用命令对象作为桥梁实现调用者和具体实现者之间的解耦和交互。


命令模式的特点:


  • 将发出请求的对象和执行请求的对象解耦
  • 调用者可以自由定义命令参数进行自由的组合
  • 命令可以用来实现日志或者事务系统(undo操作)


命令模式结构图:


下面根据命令模式的定义,以及上面对于命令模式的理解,构建具体的结构图


网络异常,图片无法展示
|


+ Client 客户端:客户端需要创建具体的命令类,并且通过发送请求给执行者调用具体的对象,发送方和接收方不存在关联,统一由命令对象进行连接。

+ Invoker 执行者:请求的发送者,负责将请求分发给具体的命令实现类,由实现类调用实际的执行者进行执行操作

+ Command 接口:命令接口,定义命令的规范

+ ConcreteCommand 命令接口实现类:实现命令的同时组合具体对象。

+ ConcreteObject 具体实现类:定义截图的实现生产对象。

+ Receive 执行者:请求的真正执行者,可以是任意对象,通常以 组合形式出现在执行者的内部


命令模式的理解


这里参考**《Head firtst设计模式》**的案例,模拟具体的交互流程


对象村餐厅交互过程


我们到餐厅点餐,一般会经历如下的流程

  1. 客人负责下订单,由服务员接受订单
  2. 服务器接收订单,调用订单柜台的下订单的方法,不需要关注细节
  3. 订单柜台通知厨师进行生产
  4. 厨师生产订单物品之后,交给服务员上菜

根据上面的步骤利用伪代码的表现如下:

  • createCommandObject() 构建命令对象
  • setCommand() 传递命令
  • execute() 命令执行
  • action1()action2() 执行者实际执行


交互流程图


我们根据上面的交互过程介绍,构建具体的交互流程图,我们可以看到里面有角色:客人服务员订单柜台厨师,他们本身并没有关联,而是通过餐厅的形式彼此产生了具体的关联,同时我们对比上面的结构图,看下对象村餐厅对应的结构图:


网络异常,图片无法展示
|


下面根据结构图说一下各种角色的职责:

客人:相当于client客户端,负责指挥服务员进行下单的操作。

服务员:充当请求的发送者,接受客户端的请求,调用下订单的接口到具体的订单柜台,但是不需要关心具体的细节,只具备下订单这一个操作

订单柜台:通过服务员传递的订单,安排厨师执行具体的任务

厨师:根据订单柜台的订单做菜,将结果返回给服务员(或客人)

我们从上面的角色图再来看具体的命令模式定义,可以看到基本都是一一对应的情况。


网络异常,图片无法展示
|


命令模式和策略模式的对比


命令模式和策略模式的结构图有些许的类似,下面我们来对比看一下这两张图的异同:

策略模式结构图:


网络异常,图片无法展示
|


命令模式结构图:


网络异常,图片无法展示
|


相同点:


  1. 命令模式通过定义命令规范接口,由子类实现命令的执行细节,策略同样定义策略行为同时用子类实现不同的策略功能
  2. 命令模式和策略都解耦了请求的发送者和执行者


不同点:


  1. 命令模式利用了命令组合执行对象的形式执行实现具体实现,而策略模式依靠上下文对象进行切换
  2. 策略模式针对某个对象实现不同的策略效果,而命令模式关注请求发送者和实现者之间的业务解耦组合


实战



模拟场景:


这次的案例还是模拟《Head First》设计模式的当中对于遥控器遥控电器的一个案例,我们定义如下的内容:

遥控器:命令的发送方,负责根据不同的操作按钮调用不同的设备工作,生成具体的命令对象调用接口执行具体的命令

命令接口:负责定义命令的实现规范,充当遥控器里面的每一个按钮,对应都有具体的实现

命令实现类:负责实现命令的接口,同时调用具体的实现对象执行命令

实现对象:命令的真正执行者,一般夬在命令实现类的内部,比如电视,灯泡等


不使用设计模式


在不使用设计模式的情况下,我们通常通过对象组合的形式组合不同的实体对象执行命令,下面通过一些简单的代码说明一下设计的弊端:


// 灯泡
public class Light {
    public void on(){
        System.out.println("打开灯光");
    }
    public void off(){
        System.out.println("关闭灯光");
    }
}
// 电视机
public class Television {
    public void on(){
        System.out.println("打开电视");
    }
    public void off(){
        System.out.println("关闭电视");
    }
}
// 遥控器
public class RemoteControl {
    private Light light;
    private Television television;
    public RemoteControl(Light light, Television television) {
        this.light = light;
        this.television = television;
    }
    public void button1(){
        light.on();
    }
    public void button2(){
        television.on();
    }
}
// 单元测试
public class Main {
    public static void main(String[] args) {
        Television television = new Television();
        Light light = new Light();
        RemoteControl remoteControl = new RemoteControl(light, television);
        remoteControl.button1();
        remoteControl.button2();
    }
}/*运行结果:
打开灯光
打开电视
*/
复制代码


从上面的简单代码可以看到,如果我们继续增加电器,同时增加方法,不仅会导致遥控器要随着电器的改动不断改动,同时每次新增一个电器,遥控器要进行类似“注册”的行为,需要将电器接入到遥控器,这样显然是不符合逻辑的,因为我们都知道,遥控器是单纯的指挥者,他不参与任何命令的操作细节,同时虽然真正工作的方法是具体对象的方法,但是这种形式类似将电器“塞”到了遥控器的内部执行,这样也是存在问题,我们下面需要修改一下这种严重耦合的设计。


使用命令模式改写:


我们按照命令模式的结构图,改写案例,我们需要定义下面的类和对应的接口:


+ RemoteControl 遥控器
+ Command(接口) 命令规范接口,用于接入到遥控器内部
+ LightCommandConcrete 控制电器的亮灭命令实现
+ SwitchCommandConcrete 控制电器的开关命令实现
+ Light 灯泡
+ Television 电视机
复制代码


首先,我们定义命令的接口,定义接口的规范方法。然后定义实现子类实现不同命令的操作效果,在命令实现类的内部,我们组合实际执行对象,在接口方法调用实际的对象方法,这样就做到了执行者和发送者之间的解耦。


接着,我们改写控制器,他不在持有任何实际的对象方法,通过组合命令的接口,让客户端传入实现的功能,通过这种方式,遥控器不在需要依赖具体的电器实现调用具体方法,而是关注命令的接口方法,一切的细节都在命令的子类内部。

下面代码是依照命令模式进行的最简单的一个实现。


// 命令接口
public interface Command {
    /**
     * 接口备份
     */
    void execute();
}
public class LightCommandConcrete implements Command {
    private Light light = new Light();
    @Override
    public void execute() {
        light.on();
    }
}
public class SwitchCommandConcrete implements Command{
    private Television television = new Television();
    @Override
    public void execute() {
        television.on();
    }
}
// 遥控器
public class RemoteControl {
    private Command command;
    public RemoteControl(Command command) {
        this.command = command;
    }
    public void execute(){
        command.execute();
    }
    public Command getCommand() {
        return command;
    }
    public void setCommand(Command command) {
        this.command = command;
    }
}
public class Main {
    public static void main(String[] args) {
        RemoteControl remoteControl = new RemoteControl(new LightCommandConcrete());
        remoteControl.execute();
        remoteControl.setCommand(new SwitchCommandConcrete());
        remoteControl.execute();
    }
}
复制代码


经过上面的代码改造,我们成功上面的代码改造为命令模式的代码,使用设计模式之后,我们将调用者和实际执行者进行了解耦,控制器不需要知道执行的细节,只需要组合自己的命令接口,由客户端指定希望实现的内容,执行相对应的具体命令。


案例的额外扩展:


下面是对应案例如何进行后续的扩展,对于这部分内容文章篇幅有限,同时本着不重复造轮子的理念,请阅读**《Head First设计模式》**关于命令模式这一个章节,同时安利一下这本书,非常通俗易懂的讲解设计模式,对于个人的提升帮助很大。


对于上面的设计,如何加入Undo的操作?


Undo是一个很常见的功能,如果想要让Undo的操作集成到案例内部,需要按照如下的步骤进行操作:


  1. Command 接口增加Undo的操作,让所有命令支持undo
  2. 在控制器记录最后一个命令的执行对象,记录最后的操作命令,实现控制器支持undo操作
  3. 具体Command实现增加对于undo()方法调用,并且根据实际的组合对象调用方法
  4. 具体实现类实现undo()操作的具体行为效果。


如果undo里面,存在一些变量如何处理?


在命令的实现类内部,需要增加一个最后变量值的记录,用于记录当前最后一步操作的属性和变量


如何做到宏命令?


实现一个命令类,通过组合数组或者堆栈组合多个其他命令对象,通过for循环的形式依次调用。

undo也可以使用这种方式进行调用的,但是要注意**调用的顺序相反


命令模式的优缺点:


优点:

  • 命令模式实现了请求发送方和实现方解耦,不论是发送方还是接收方都不需要
  • 命令模式可以实现不同实现对象的自由组合,通过命令组合可以实现一连串简单功能

缺点:

  • 和策略模式类似,命令模式很容易造成子类的膨胀


总结:


命令模式是一种非常常见的设计模式,这种模式更多的关注点是解耦请求的发送方和实现方,命令模式在系统设计中使用还是十分常见的,是一种值得关注的设计模式。

相关文章
|
7月前
|
设计模式
设计模式之命令模式
设计模式之命令模式
|
设计模式 存储 测试技术
设计模式-行为型模式:命令模式
设计模式-行为型模式:命令模式
|
8月前
|
设计模式 Java
设计模式-命令模式
设计模式-命令模式
51 1
|
8月前
|
设计模式 存储 Java
【设计模式】命令模式
【设计模式】命令模式
|
8月前
|
设计模式 存储 前端开发
【设计模式】之命令模式
命令模式是一种非常有用的设计模式,在前端开发中经常用于管理和执行操作。它通过将操作封装成对象,并将其作为参数传递、存储或记录,实现了优雅地管理和执行操作。通过使用命令模式,我们可以提高代码的可维护性和可扩展性。然而,在应用命令模式时需要权衡其带来的优缺点,并根据具体情况进行选择。
72 0
|
设计模式 C++
设计模式之命令模式(C++)
设计模式之命令模式(C++)
|
设计模式
设计模式行为模式-命令模式
设计模式行为模式-命令模式
116 2
|
设计模式 消息中间件 Java
一起来学设计模式之命令模式
前言 目前正在出一个设计模式专题系列教程, 篇幅会较多, 喜欢的话,给个关注❤️ ~ 本节给大家讲一下设计模式中的命令模式,并结合实际业务场景给大家讲解如何使用~ 本专题的所有案例代码主要以Java语言为主, 好了, 废话不多说直接开整吧~ 命令模式 命令模式(Command Pattern)是一种行为型设计模式,它允许将请求封装成对象,从而使您可以将不同的请求参数化,将请求排队或记录请求日志,以及支持可撤销操作。在这个模式中,请求被封装成一个对象,并在发送者和接收者之间解耦。命令模式允许在不更改现有客户端代码的情况下添加新命令。
|
设计模式
浅谈设计模式 - 命令模式(七)
命令模式也是一种比较常见的行为型模式,可以想象我们的手机智能遥控器,通过按动按钮的形式开启各种家具,说白了,就是将一系列的请求命令封装起来,不直接调用真正执行者的方法,这样比较好扩展。需要注意的是命令模式和策略模式相似,所以有时候可能容易弄混,这篇文章将会详细介绍命令模式
106 0
|
设计模式 Java Spring
《设计模式》命令模式
《设计模式》命令模式
119 0
《设计模式》命令模式