行为型设计模式07-命令模式

news2025/2/12 13:02:22

🧑‍💻作者:猫十二懿

❤️‍🔥账号:CSDN 、掘金 、个人博客 、Github

🎉公众号:猫十二懿

命令模式

1、命令模式介绍

命令模式(Command),将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可 撤销的操作。

1.1 命令模式基本实现

命令模式(Command Pattern)结构图

image-20230520145520053

在命令模式中,介绍上图中核心角色:

  1. 命令(Command):该角色定义了执行操作的接口,通常包含一个执行方法(execute)。
  2. 具体命令(ConcreteCommand):该角色实现了命令接口,持有对一个接收者对象的引用,并将请求转发给接收者执行具体的操作。
  3. 接收者(Receiver):该角色负责具体执行命令所指定的操作。
  4. 调用者(Invoker):该角色负责调用命令对象执行请求。
  5. 客户端(Client):该角色创建具体的命令对象,并设置命令的接收者。

通过命令模式,客户端与调用者之间的耦合可以被解耦,客户端只需创建具体的命令对象并将其传递给调用者,而不需要了解具体的接收者和操作细节。这样可以实现请求的发送者和接收者之间的解耦,并且支持对请求进行排队、记录日志、撤销和重做等操作。

Command类,用来声明执行操作的接口。

/**
 * @author Shier
 * CreateTime 2023/5/20 14:58
 * 抽象命令类
 */
public  abstract class Command {
    protected Receiver receiver;

    public Command(Receiver receiver) {
        this.receiver = receiver;
    }

    /**
     * 执行命令
     */
    public abstract void excuteCommand();
}

ConcreteCommand类,将一个接收者对象绑定于一个动作,调用接收者相应的操作,以实现executeCommand。

/**
 * @author Shier
 * CreateTime 2023/5/20 15:01
 * 具体执行命令
 */
public class ConcreteCommand extends Command{

    public ConcreteCommand(Receiver receiver) {
        super(receiver);
    }

    @Override
    public void excuteCommand() {
        receiver.action();
    }
}

Invoker类,要求该命令执行这个请求。

/**
 * @author Shier
 * CreateTime 2023/5/20 15:02
 */
public class Invoker {
    private Command command;

    public void setCommand(Command command) {
        this.command = command;
    }
    public void excuteCommand(){
        command.excuteCommand();
    }
}

Receiver类,知道如何实施与执行一个与请求相关的操作,任何类都可 能作为一个接收者。

/**
 * @author Shier
 * CreateTime 2023/5/20 15:00
 */
public class Receiver {
    public void action(){
        System.out.println("执行请求!!");
    }
}

客户端代码,创建一个具体命令对象并设定它的接收者。

/**
 * @author Shier
 * CreateTime 2023/5/20 15:03
 */
public class ComClient {
    public static void main(String[] args) {

        Receiver receiver = new Receiver(); // 将具体怎么执行请求封装
        ConcreteCommand command = new ConcreteCommand(receiver); 
        Invoker invoker = new Invoker();  // 将调用命令执行请求
        invoker.setCommand(command);
        invoker.excuteCommand();
    }
}

2、具体例子说明

摆摊烧烤 VS 开店烧烤:

摆摊烧烤:就一个人进行烧烤,客户一多,就很难记清楚谁点了什么烧烤,要什么程度的烧烤(不辣、微辣等),而且这里需要知道谁是烧烤者

开店烧烤:有服务员记录了你点的烧烤,同时是服务员通知后厨去进行烧烤,客户根本就不知道谁烧的,还有等你觉得点了太多的烧烤,还可以叫服务员撤销一些,不够吃再点同样可以的。这样就避免了混乱的情况发生。服务员做记录在程序里面就是日志信息

2.1 摆摊烧烤例子 - 不使用命令模式

//烤肉串者
class Barbecuer{
    //烤羊肉
    public void bakeMutton(){
        System.out.println("烤羊肉串!");
    }
    //烤鸡翅
    public void bakeChickenWing(){
        System.out.println("烤鸡翅!");
    }
}

客户端代码:

public class Test {

    public static void main(String[] args) {
        Barbecuer boy = new Barbecuer();

        boy.bakeMutton();
        boy.bakeMutton();
        boy.bakeMutton();
        boy.bakeChickenWing();
        boy.bakeMutton();
        boy.bakeMutton();
        boy.bakeChickenWing();
    }
}

存在问题:用户多了,请求就多,就容易混乱。使用命令模式进行改进,也就是进步到开店烧烤的程度了

2.2 开店烧烤 - 使用命令模式

看看代码结构图:

image-20230520151909152

烧烤类:

/**
 * @author Shier
 * CreateTime 2023/5/20 15:20
 * 烧烤者
 */
public class Barbecuer {
    public void bakeMutton(){
        System.out.println("烤羊肉串!");
    }
    public void bakeChickenWing(){
        System.out.println("考鸡翅!");
    }
}

抽象命令类:

/**
 * @author Shier
 * CreateTime 2023/5/20 15:21
 * 抽象命令类
 */
public abstract class Command {
    public Barbecuer barbecuer;

    public Command(Barbecuer barbecuer) {
        this.barbecuer = barbecuer;
    }
    /**
     *执行命令
     */
    public abstract void excuteCommand();
}

具体命令类(烤什么东西):

/**
 * @author Shier
 * CreateTime 2023/5/20 15:22
 * 烤羊肉命令类
 */
public class BakeMuttonCommand extends Command {
    public BakeMuttonCommand(Barbecuer barbecuer) {
        super(barbecuer);
    }

    @Override
    public void excuteCommand() {
        barbecuer.bakeMutton();
    }
}
/**
 * @author Shier
 * CreateTime 2023/5/20 15:22
 * 烤鸡翅命令类
 */
public class BakeChickenWingCommand extends Command {
    public BakeChickenWingCommand(Barbecuer barbecuer) {
        super(barbecuer);
    }

    @Override
    public void excuteCommand() {
        barbecuer.bakeChickenWing();
    }
}

服务员:

/**
 * @author Shier
 * CreateTime 2023/5/20 15:24
 * 服务员
 */
public class Waiter {
    private Command command;

    public void setCommand(Command command) {
        this.command = command;
    }

    /**
     * 通知后厨进行烧烤
     */
    public void notifyCommand(){
        command.excuteCommand();
    }
}

客户端:

/**
 * @author Shier
 * CreateTime 2023/5/20 15:25
 */
public class BarBecuerClient {
    public static void main(String[] args) {
        //开店前工作
        Barbecuer barbecuer = new Barbecuer(); // 后厨厨师
        BakeMuttonCommand muttonCommand = new BakeMuttonCommand(barbecuer); // 厨师进行烤羊肉串
        BakeChickenWingCommand chickenWingCommand = new BakeChickenWingCommand(barbecuer); // 厨师烤鸡翅
        Waiter waiter = new Waiter(); // 服务员

        // 开门营业
        waiter.setCommand(muttonCommand); // 下单烤羊肉串
        waiter.notifyCommand(); // 通知厨师烤羊肉串
        waiter.setCommand(chickenWingCommand); // 下单烤鸡翅
        waiter.notifyCommand(); // 通知厨师烤鸡翅
    }
}

结果:

烤羊肉串!
考鸡翅!

但是你有没有发现问题呀,客户每次下单完成一个服务员就要立马去通知厨师,这样子你的服务员很快就会走了,没人愿意来帮你这样干(累死累活挣几个钱)

存在以下几个问题:

  1. 第一,真实的情况其实并不是用户点一个菜,服务员就通知 厨房去做一个,那样不科学,应该是点完烧烤后,服务员一次通知制作;
  2. 第 二,如果此时鸡翅没了,不应该是客户来判断是否还有,客户哪知道有没有呀,应该是服务员或烤肉串者来否决这个请求;
  3. 第三,客户到底点了哪些烧烤或饮料,这是需要记录日志的,以备收费,也包括后期的统计;
  4. 第四,客 户完全有可能因为点的肉串太多而考虑取消一些还没有制作的肉串。这些问题都需要得到解决。

解决办法:

重构 一 下服务员 Waiter 类 , 尝试改一下 。 将 private Command command;改成一个ArrayList,就能解决了

/**
 * @author Shier
 * CreateTime 2023/5/20 15:24
 * 服务员
 */
public class Waiter {
    /**
     * 增加存放具体命令的容器
     */
    private ArrayList<Command> orders = new ArrayList<Command>();

    /**
     * 下单
     *
     * @param commands
     */
    public void setOrders(Command commands) {
        String name = commands.getClass().getSimpleName();

        if (name.equals("BakeChickenWingCommand")) {
            System.out.println("服务员:鸡翅没有了,请点别的烧烤");
        } else {
            this.orders.add(commands);
            System.out.println("增加订单:" + name + " 时间:" + getNowTime());
        }
    }

    //取消订单
    public void cancelOrder(Command command) {
        String className = command.getClass().getSimpleName();
        // 可以取消部门订单
        orders.remove(command);
        System.out.println("取消订单:" + className + " 时间:" + getNowTime());
    }

    //通知执行
    public void notifyCommand() {
        // 根据用户点的烧烤订单通知后厨制作
        for (Command command : orders) {
            command.excuteCommand();
        }
    }

    private String getNowTime() {
        SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss");
        return formatter.format(new Date()).toString();
    }
}
/**
 * @author Shier
 * CreateTime 2023/5/20 15:25
 */
public class BarBecuerClient {
    public static void main(String[] args) {
        //开店前工作
        Barbecuer barbecuer = new Barbecuer(); // 后厨厨师
        BakeMuttonCommand muttonCommand = new BakeMuttonCommand(barbecuer); // 厨师进行烤羊肉串
        BakeChickenWingCommand chickenWingCommand = new BakeChickenWingCommand(barbecuer); // 厨师烤鸡翅
        Waiter waiter = new Waiter(); // 服务员

        System.out.println("开门营业");
        waiter.setOrders(muttonCommand); // 下单烤羊肉串
        waiter.setOrders(muttonCommand); // 下单烤羊肉串
        waiter.setOrders(muttonCommand); // 下单烤羊肉串
        waiter.setOrders(muttonCommand); // 下单烤羊肉串
        waiter.setOrders(muttonCommand); // 下单烤羊肉串
        waiter.cancelOrder(muttonCommand); // 取消一穿羊肉串

        waiter.setOrders(chickenWingCommand); // 下单烤鸡翅
        System.out.println("点菜完成,通知后厨烧菜");
        waiter.notifyCommand(); // 通知厨师
    }
}

输出结果:

结果

这样就是比较符合常理的烧烤点菜,说着也饿了,下面再来总结一个命令模式

3、命令模式总结

  • 命令模式的作用
    1. 它能较容易地设计一个命令队列。
    2. 在需要的情况下,可以较容易地将命令记入日志。
    3. 允许接收请求的一方决定是否要否决请求。
    4. 可以容易地实现对请求的撤销和重做
    5. 由于加进新的具体命令类不影响其他的类,因此增加新的具体命令类很容易

命令模式把请求一个操作的对象与知道怎么执行一个 操作的对象分割开

一切都要根据实际的业务情况来开发,不要过度开发,在敏捷开发原则中:

不要为代码添加基于猜测的、实际不需要的功能。如果不清楚一个系统是否需要命令模式,一般就不要着急去实现它,事实上,在需要的时候通过重构实现这个模式并不困 难,只有在真正需要如撤销/恢复操作等功能时,把原来的代码重构为命令模式才有意义。

  • 命令模式的优点:

    • 解耦合:将请求的发送者和接收者解耦,使得发送者不需要知道具体的接收者对象,从而减少了两者之间的依赖。
    • 扩展性:可以很容易地新增和组合命令对象,而不需要修改现有的代码,实现了开闭原则。
    • 支持撤销和重做:由于命令对象封装了操作,可以轻松地实现撤销和重做功能。
    • 支持队列和日志:命令对象可以被放入队列中,支持延迟执行和排队执行;同时也可以用于记录操作日志和恢复状态。
  • 命令模式的缺点:

    • 增加了类和对象的数量,引入了额外的复杂性。
    • 可能导致系统中的命令类过多,需要合理地管理和设计命令类的数量和关系。
  • 命令模式适用于以下场景:

    • 需要将请求发送者和请求接收者解耦的场景。
    • 需要支持请求的队列、撤销、重做等操作的场景。
      般就不要着急去实现它,事实上,在需要的时候通过重构实现这个模式并不困 难,只有在真正需要如撤销/恢复操作等功能时,把原来的代码重构为命令模式才有意义。
  • 命令模式的优点:

    • 解耦合:将请求的发送者和接收者解耦,使得发送者不需要知道具体的接收者对象,从而减少了两者之间的依赖。
    • 扩展性:可以很容易地新增和组合命令对象,而不需要修改现有的代码,实现了开闭原则。
    • 支持撤销和重做:由于命令对象封装了操作,可以轻松地实现撤销和重做功能。
    • 支持队列和日志:命令对象可以被放入队列中,支持延迟执行和排队执行;同时也可以用于记录操作日志和恢复状态。
  • 命令模式的缺点:

    • 增加了类和对象的数量,引入了额外的复杂性。
    • 可能导致系统中的命令类过多,需要合理地管理和设计命令类的数量和关系。
  • 命令模式适用于以下场景:

    • 需要将请求发送者和请求接收者解耦的场景。
    • 需要支持请求的队列、撤销、重做等操作的场景。
    • 需要支持日志记录、事务管理等功能的场景。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/639873.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux后端服务器开发】常用开发工具

目录 一、apt / yum 二、gcc / g 三、make / makefile 四、vi / vim 五、gdb 一、apt / yum apt 和 yum 都是在Linux环境下的软件包管理器&#xff0c;负责软件的查找、安装、更新与卸载。 apt 是Ubuntu系统的包管理器&#xff0c;yum是Centos系统的包管理器&#xff0c…

SQL回顾总结,超级全

SELECT&#xff1a;语句用于从数据库中选取数据 从 "Websites" 表中选取 "name" 和 "country" 列 SELECT name,country FROM Websites 从 "Websites" 表中选取所有列 SELECT * FROM Websites; SELECT DISTINCT&#xff1a;用于返…

Android系统视角下对APK的分析(2)- APK安装过程的定性分析

声明 以Android手机用户角度来看&#xff0c;安装各式各样的APP&#xff0c;基本就是从应用市场上 “搜索->下载->安装” 三连。而对Android系统来说&#xff0c;这就是个大工程了&#xff0c;因为对Android系统来说APK是“外来户”&#xff0c;如何安装它、有限制地支持…

linux实验三 vi编辑器及用户管理

1、vi编辑器的详细使用 &#xff08;1&#xff09;在用户主目录下建一个名为vi的目录。 &#xff08;2&#xff09;进入vi目录。 &#xff08;3&#xff09;将文件/etc/man_db.conf复制到当前目录下&#xff0c;并用命令sudo修改man_db.conf的属性为所有用户可以读写。 &am…

.net版本下载

1先登录微软 Microsoft - 云、计算机、应用和游戏 下载 .NET Framework | 免费官方下载

【Oauth2请求不带client_id,获取方法】

文章目录 前言一、关键&#xff1a;请求头 Basic xxx:xxx二、源码分析BasicAuthenticationFilter 类extractAndDecodeHeader 方法authenticate方法loadUserByUsername 方法 总结 前言 这段时间在学习 oauth2, 发现我组用的框架&#xff0c;登录请求参数中并没有 client_id &a…

JVM 面试必会面试题

1. 说一说JVM的主要组成部分 点击放大看&#xff0c;一图胜千文 jvm 方法区和堆是所有线程共享的内存区域&#xff1b;而虚拟机栈、本地方法栈和程序计数器的运行是线程私有的内存区域&#xff0c;运行时数据区域就是我们常说的JVM的内存。类加载子系统&#xff1a;根据给定的…

Altium Designer二次开发

Altium Designer二次开发就在该软件原有的基础上&#xff0c;自己写代码给它添加新功能&#xff0c;如&#xff1a;一键生成Gerber&#xff0c;计算铺铜面积&#xff0c;PCB走线的寄生参数和延时等等。 Altium Designer二次开发有两种方式&#xff0c;一种是基于Altium Designe…

Hadoop集群部署和启动与关闭

Hadoop集群的部署方式分为三种&#xff0c;分别是独立模式&#xff08;Standalone mode&#xff09;、伪分布式模式&#xff08;Pseudo-Distributed mode&#xff09;和完全分布式模式&#xff08;Cluster mode&#xff09;&#xff0c;独立模式和伪分布式模式主要用于学习和调…

Day974.授权码和访问令牌的颁发流程 -OAuth 2.0

授权码和访问令牌的颁发流程 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于授权码和访问令牌的颁发流程的内容。 授权服务就是负责颁发访问令牌的服务。更进一步地讲&#xff0c;OAuth 2.0 的核心是授权服务&#xff0c;而授权服务的核心就是令牌。 为什么这么说…

被冻结的层在训练过程中参与正向反向传递,只是这一层的梯度不再更新。||底层逻辑

被冻结的层可以前向传播,也可以反向传播,只是自己这一层的参数不更新,其他未冻结层的参数正常更新。 在微调期间&#xff0c;只有被激活的层的梯度会被计算和更新&#xff0c;而被冻结的层的梯度则会保持不变。 其实从数学上去理解也不难&#xff0c;但自己手推还是需要花点时…

《自然》:DeepMind推出AlphaDev或将加速全球计算

数字世界对计算和能源的需求正在不断增加。在过去的五十年中&#xff0c;人类主要依靠硬件层面的改进来满足这一点。然而&#xff0c;随着微芯片接近其物理极限&#xff0c;改进计算机运行代码&#xff0c;以使计算算力更强大和可持续&#xff0c;变得至关重要。对于每天运行数…

线程的生命周期

我是一个线程 第一回 初生牛犊 我是一个线程&#xff0c;我一出生就被编了个号: 0x3704&#xff0c;然后被领到一个昏暗的屋子里&#xff0c;在这里我发现了很多和我一模一样的同伴。 我身边…

一文教你如何在数据库中安全地存储密码

前言 作者&#xff1a;神的孩子在歌唱 大家好&#xff0c;我叫智 让我们先谈谈什么不该做。 不要以明文形式存储密码。任何具有数据库内部访问权限的人都可以看到它们。如果数据库受损&#xff0c;攻击者可以轻松获取所有密码。那么&#xff0c;我们应该如何在数据库中安全地存…

10个ai算法常用库java版

今年ChatGPT 火了半年多,热度丝毫没有降下来。深度学习和 NLP 也重新回到了大家的视线中。有一些小伙伴问我,作为一名 Java 开发人员,如何入门人工智能,是时候拿出压箱底的私藏的学习AI的 Java 库来介绍给大家。 这些库和框架为机器学习、深度学习、自然语言处理等提供了广…

OceanBase 安全审计之身份鉴别

本文主要以 MySQL 和 OceanBase 对比的方式&#xff0c;来介绍 OceanBase&#xff08;MySQL 模式&#xff09;安全体系中关于身份鉴别的相关内容&#xff0c;包括身份鉴别机制、用户名组成、密码复杂度、密码过期策略等。 作者&#xff1a;金长龙 爱可生测试工程师&#xff0c;…

快速掌握SQL语言——数据查询语言DQL

0️⃣前言 数据查询语言DQL是一种用于查询数据库中数据的语言&#xff0c;它是SQL的一部分&#xff0c;也是SQL中最常用的语言之一。 文章目录 0️⃣前言1️⃣介绍2️⃣使用3️⃣重要性4️⃣总结 1️⃣介绍 DQL&#xff08;Data Query Language&#xff09; 主要用于从数据库中…

为PyCharm IDE 配置三剑客:QtDesigner[可视化设计器]、PyUIC[可视化设计器ui文件转py代码]、PyRcc[资源文件转py代码]

过去一直在使用Eric6 PyQt5&#xff0c;然而最近它[已然是古董级的了]似乎有些不太正常&#xff0c; 像我这样有强迫症的人怎么可以容忍呢? 于是有了换IDE的想法&#xff0c;听说PyCharm是个高富帅&#xff0c;大家都很喜欢用它呢&#xff01;于是乎才有了这篇文章。 前提条…

【C++ 笔记五】STL 标准模板库 —— 容器基础进阶

【C 笔记五】STL 标准模板库 —— 容器基础进阶 文接上文 【C 笔记四】STL 标准模板库 —— 容器基础 文章目录 【C 笔记五】STL 标准模板库 —— 容器基础进阶I - 简单回顾1.1 - 序列式容器&#xff08;顺序容器&#xff09;1.2 - 关联式容器 (关联容器)1.3 - 访问方法/对外接…

面试-java常见问题

JVM 配置 程序计数器&#xff1a;当前线程所执行的字节码的行号指示器java虚拟机栈:临时变量元空间&#xff1a;类常量池&#xff0c;运行时常量池方法区&#xff1a;类信息&#xff0c;静态变量堆&#xff1a;对象实例&#xff0c;Sting常量池等 类加载过程 加载->链接&am…