命令设计模式

简介

命令模式(Command Pattern)是对命令的封装,每一个命令都是一个操作:请求方发出请求要求执行一个操作;接收方收到请求,并执行操作。命令模式解耦了请求方和接收方,请求方只需请求执行命令,不用关心命令怎样被接收、怎样被操作及是否被执行等。命令模式属于行为型设计模式。

通用模板

  1. 创建接收者角色:该类负责具体实施或执行一个请求。

    // 接收者
    public class Receiver {public void action() {System.out.println("执行具体操作");}
    }
    
  2. 创建抽象命令角色:定义需要执行的所有命令行为。

    // 抽象命令接口
    public interface ICommand {void execute();
    }
    
  3. 创建具体命令角色:该类内部维护一个Receiver,在其execute()方法中调用Receiver的相关方法。

    // 具体命令
    public class ConcreteCommand implements ICommand {// 直接创建接收者,不暴露给客户端private Receiver receiver = new Receiver();@Overridepublic void execute() {this.receiver.action();}
    }
    
  4. 创建请求者角色:接收客户端的命令,并执行命令。

    // 请求者
    public class Invoker {private ICommand cmd;public Invoker(ICommand cmd) {this.cmd = cmd;}public void action(){this.cmd.execute();}
    }
    

模板测试

  1. 测试代码
    public class Client {public static void main(String[] args) {ConcreteCommand cmd = new ConcreteCommand();Invoker invoker = new Invoker(cmd);invoker.action();}
    }
    
  2. 测试结果
    执行具体操作
    

应用场景

在日常生活中,命令模式是很常见的。比如,经历过黑白电视机年代的小伙伴应该都有过这样的经历。那个年代在看电视的时候,想要换个频道简直不容易。我们得走到电视机前扭动换台的旋钮,一顿“咔咔咔”折腾才能完成频道的切换。如今,遥控器的发明简直就是“解放战争”,我们躺在沙发上只需要轻轻一按遥控器即可完成频道的切换。这就是命令模式,将换台请求和换台处理完全解耦了。
当系统的某项操作具备命令语义,且命令实现不稳定(变化)时,可以通过命令模式解耦请求与实现,使用抽象命令接口使请求方的代码架构稳定,封装接收方具体命令的实现细节。接收方与抽象命令接口呈现弱耦合(内部方法无须一致),具备良好的扩展性。命令模式主要适用于以下应用场景。 (1)现实语义中具备“命令”的操作(如命令菜单、Shell命令等)。
(2)请求的调用者和接收者需要解耦,使得调用者和接收者不直接交互。
(3)需要抽象出等待执行的行为,比如撤销(Undo)操作和恢复(Redo)等操作。
(4)需要支持命令宏(即命令组合操作)。

优点

(1)通过引入中间件(抽象接口),解耦了命令请求与实现。
(2)扩展性良好,可以很容易地增加新命令。
(3)支持组合命令,支持命令队列。
(4)可以在现有命令的基础上,增加额外功能。比如日志记录,结合装饰器模式会更加灵活。

缺点

(1)具体命令类可能过多。
(2)命令模式的结果其实就是接收方的执行结果,但是为了以命令的形式进行架构、解耦请求与实现,引入了额外类型结构(引入了请求方与抽象命令接口),增加了理解上的困难。不过这也是设计模式的通病,抽象必然会额外增加类的数量;代码抽离肯定比代码聚合更难理解。

“生搬硬套”实战

场景描述

我们可以用一个遥控器控制家用电器(如电灯)作为例子。在这个场景中,遥控器是发出命令的对象,而家用电器则是执行命令的对象。

代码开发
  1. 创建接收者角色(这里指的是实际执行的对象灯)

    // 定义接收者类,即实际执行动作的对象——Light
    public class Light {public void turnOn() {System.out.println("Light is on");}public void turnOff() {System.out.println("Light is off");}
    }
    
  2. 创建抽象命令角色:(这里指的是抽象命令接口,定义执行和取消操作)

    // 定义一个命令接口
    public interface ICommand {void execute();void undo();
    }
    
  3. 创建具体命令角色(这里指的是开灯和关灯的具体命令实现类)

    // 具体的开灯命令类
    public class LightOnCommand implements ICommand{private final Light light;public LightOnCommand(Light light) {this.light = light;}@Overridepublic void execute() {light.turnOn();}@Overridepublic void undo() {light.turnOff();}
    }
    
    // 具体关灯命令类
    public class LightOffCommand implements ICommand {private final Light light;public LightOffCommand(Light light) {this.light = light;}@Overridepublic void execute() {light.turnOff();}@Overridepublic void undo() {light.turnOn();}
    }
    
  4. 创建请求者角色(这里指的就是遥控器)

    // 定义一个调用者——遥控器类,它将保存命令并执行
    public class RemoteControl {private ICommand command;public void setCommand(ICommand command) {this.command = command;}public void pressButton() {command.execute();}public void undo() {command.undo();}
    }
    

至此,我们就通过“生搬硬套”命令模式的模板设计出一套遥控器开关灯的案例,接下来我们进行测试:

  • 测试代码

    public class Client {public static void main(String[] args) {Light light = new Light();RemoteControl remote = new RemoteControl();// 设置打开电灯的命令remote.setCommand(new LightOnCommand(light));remote.pressButton(); // 执行命令:打开电灯remote.undo(); // 撤销命令:关闭电灯// 设置关闭电灯的命令remote.setCommand(new LightOffCommand(light));remote.pressButton(); // 执行命令:关闭电灯remote.undo(); // 撤销命令:打开电灯}
    }
    
  • 测试结果

    Light is on
    Light is off
    Light is off
    Light is on
    
场景描述2

假如我们开发一个播放器,播放器有播放功能、拖动进度条功能、停止播放功能、暂停功能,我们在操作播放器的时候并不是直接调用播放器的方法,而是通过一个控制条去传达指令给播放器内核,具体传达什么指令,会被封装为一个个按钮。那么每个按钮就相当于对一条命令的封装。用控制条实现了用户发送指令与播放器内核接收指令的解耦。

代码开发2
  1. 创建接收者角色(这里指的是实际执行的对象播放器)

    // 定义接收者类,即实际执行动作的对象——播放器
    public class GPlayer {public void play() {System.out.println("正常播放");}public void speed() {System.out.println("拖动进度条");}public void stop() {System.out.println("停止播放");}public void pause() {System.out.println("暂停播放");}
    }
    
  2. 创建抽象命令角色:(这里指的是抽象命令接口,定义执行命令操作)

    // 定义一个命令接口
    public interface IAction {void execute();
    }
    
  3. 创建具体命令角色(这里指的是播放、暂停、停止、加速具体命令实现类)

    // 具体的播放命令
    public class PlayAction implements IAction {private GPlayer player;public PlayAction(GPlayer player) {this.player = player;}@Overridepublic void execute() {player.play();}
    }
    
    // 具体的暂停命令
    public class PauseAction implements IAction {private GPlayer player;public PauseAction(GPlayer player) {this.player = player;}@Overridepublic void execute() {player.pause();}
    }
    
    // 具体的停止命令
    public class StopAction implements IAction {private GPlayer player;public StopAction(GPlayer player) {this.player = player;}@Overridepublic void execute() {player.stop();}
    }
    
    // 具体的拖动进度条命令
    public class SpeedAction implements IAction {private GPlayer player;public SpeedAction(GPlayer player) {this.player = player;}@Overridepublic void execute() {player.speed();}
    }
    
  4. 创建请求者角色(这里指的就是播放器控制面板)

    import java.util.ArrayList;
    import java.util.List;// 定义一个调用者——控制面板类,它将保存命令并执行
    public class Controller {private List<IAction> actions = new ArrayList<>();public void addAction(IAction action) {actions.add(action);}public void execute(IAction action) {action.execute();}public void executes() {for (IAction action : actions) {action.execute();}actions.clear();}
    }
    

至此,我们就通过“生搬硬套”命令模式的模板设计出一套通过播放器控制面板上的按钮来控制播放器的案例,接下来我们进行测试:

  • 代码测试
    public class Client {public static void main(String[] args) {GPlayer player = new GPlayer();Controller controller = new Controller();controller.execute(new PlayAction(player));controller.addAction(new PauseAction(player));controller.addAction(new PlayAction(player));controller.addAction(new StopAction(player));controller.addAction(new SpeedAction(player));controller.executes();}
    }
    
  • 测试结果
    正常播放
    暂停播放
    正常播放
    停止播放
    拖动进度条
    

总结

在软件系统中,行为请求者与行为实现者通常是一种紧耦合关系,因为这样的实现简单明了。但紧耦合关系缺乏扩展性,在某些场合中,当需要对行为进行记录、撤销或重做等处理时,只能修改源码。而命令模式通过在请求与实现间引入一个抽象命令接口,解耦了请求与实现,并且中间件是抽象的,它由不同的子类实现,因此具备扩展性。所以,命令模式的本质是解耦命令请求与处理。

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

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

相关文章

银河麒麟V10安装ToDesk远程控制

银河麒麟V10安装ToDesk远程控制 ARM版本安装 1.下载arm的deb包 wget https://dl.todesk.com/linux/todesk_4.0.3_aarch64.deb2.安装 sudo apt-get install ./todesk_4.0.3_aarch64.deb3.启动todesk todesk

文献翻译用什么软件?新手建议收藏这5个

在学术研究的广阔天地里&#xff0c;语言障碍往往是科研人员不得不跨越的一道难关。 面对海量的外文文献&#xff0c;如何高效、准确地获取其中的信息&#xff0c;成为了许多学者关注的焦点。 想知道文献翻译器推荐哪一个&#xff1f;今天这篇文章为大家推荐5个优秀的文献翻译…

如何制作低代码开发的视频教程?

如何制作低代码开发的视频教程&#xff1f; 随着数字化转型的加速&#xff0c;越来越多的企业和组织开始采用低代码开发平台来加速应用程序的构建。对于许多开发者和业务人员来说&#xff0c;学习如何使用这些平台可以显著提高工作效率。因此&#xff0c;创建一份清晰、实用且…

02_InFluxDb

InFluxDb 初始化初始化流程 交互InFluxDbWebUI交互 数据模型行协议添加标签数据格式 数据类型空格索引 Flux语言 初始化 初始化流程 用户 密码 组织名称 Bucket—mysql里面的数据库概念 交互InFluxDb 暂用了8086端口.提供了 http api WebUI交互 略... 数据模型 这是mys…

无源有损耗导电介质的平面电磁波——复数介电常数带来复波数k(导致幅度衰减)和复波阻抗(带来磁场电场相位不同)

推导中以εμσ是实数为假设 注意在线性介质中J 0和σ等于0其实是一个条件&#xff0c;因为J σE 线性介质的麦克斯韦方程 线性介质无源无损耗条件下 线性介质无源有损耗导电介质下 无源有损耗的复数麦克斯韦方程组&#xff0c;只有方程二与无源无损耗的麦克斯韦方程组不同…

双十一选购攻略:2024年双十一有什么值得入手好物?

又到一年双11&#xff0c;还有很多持币观望的小伙伴&#xff0c;可能还没想要买什么&#xff0c;所以我就来啦&#xff0c;给大家参谋参谋&#xff0c;结合我生活中的好物使用经验&#xff0c;来跟大家做个分享&#xff0c;如果能给已经进入双11“买买买”节奏的你一些参考&…

java.lang.NoClassDefFoundError: kotlin/Result解决方案

问题 在控制窗口上虽然报错是找不到对应的class&#xff0c;但是呢在我们导入kotlin的后&#xff0c;还是报相同的异常&#xff0c;在网上查找了各种资料&#xff0c;都没有解决方案。 问题分析 在idea2021之后&#xff0c;kotlin都使用远程仓库&#xff08;kotlinx-coeouti…

C语言 动态数据结构的C语言实现内存映像

C程序的内存映像 C程序中变量的内存分配方式  C程序中变量的内存分配方式  从静态存储区分配  全局变量和静态变量 C程序中变量的内存分配方式  从静态存储区分配  全局变量和静态变量  在栈上分配  存放函数参数值&#xff0c;局部变量值等  …

1.1 flexsim基础入门

连线和端口 A&#xff1a;连接 S&#xff1a;中间端口连接 &#xff08;经常用于货物搬运的时候&#xff0c;中间端口连接属于无方向连接&#xff09; 假设有一个任务分配器&#xff0c;用来分配任务。暂存区与任务分配器连接&#xff0c;说明通过任务分配器作为中间商下达任务…

【重学 MySQL】五十、添加数据

【重学 MySQL】五十、添加数据 使用INSERT INTO语句添加数据基本语法示例插入多行数据注意事项 使用LOAD DATA INFILE语句批量添加数据其他插入数据的方式注意事项 在MySQL中&#xff0c;添加数据是数据库操作中的基本操作之一。 使用INSERT INTO语句添加数据 使用 INSERT IN…

GPT系列

GPT&#xff08;Generative Pre-Training&#xff09;&#xff1a; 训练过程分两步&#xff1a;无监督预训练有监督微调 模型结构是decoder-only的12层transformer 1、预训练过程&#xff0c;窗口为k&#xff0c;根据前k-1个token预测第k个token&#xff0c;训练样本包括700…

配置静态ip

背景:因业务需要需要将一台服务器从机房搬到实验室,机房是光纤,实验室是网线,需要重新配置下静态ip 确认网络配置文件(网上没找到,不清楚一下方法对不对) 先随便一个网口连接网线,执行 ifconfig -a 找到带“RUNNING”的(lo不是哈)----eno1 到/etc/sysconfig/network…

ansible 剧本模式

目录 1.剧本格式 ​编辑​编辑2.案例1创建目录分发文件剧本 2.1剧本中用到的命令 2.2书写具体剧本 3.案例2 分发 安装软件包 启动服务的剧本 3.1下载软件包 3.2用yum安装 3.3启动服务 4.找出ansible中对应的模块 5.剧本实现 4.ansible 剧本变量 4.1常用的…

代码随想录算法训练营第38天| 198.打家劫舍;213.打家劫舍II;337.打家劫舍III

第九章 动态规划part07 今天就是打家劫舍的一天&#xff0c;这个系列不算难&#xff0c;大家可以一口气拿下。 198.打家劫舍 视频讲解&#xff1a;https://www.bilibili.com/video/BV1Te411N7SX // 动态规划 class Solution {public int rob(int[] nums) {if (nums null |…

RabbitMQ入门1—queue参数之type

RabbitMQ 队列的 type 参数&#xff0c;这个参数是在 RabbitMQ 3.8.0 及以后版本引入的&#xff0c;它允许指定队列的存储和行为模式。type 参数有以下几种可选值&#xff1a; 1. classic 描述&#xff1a;这是 RabbitMQ 的传统队列类型&#xff0c;也是默认类型。如果不指定…

YOLO11涨点优化:注意力魔改 | 轻量级自注意力机制CoordAttention | CVPR2021

💡💡💡本文改进内容:CoordAttention优势,不仅会考虑输入的特征信息,还会考虑每个像素点的位置信息,从而更好地捕捉空间上的局部关系和全局关系。 💡💡💡本文改进:分别加入到YOLO11的backbone、neck、detect,助力涨点 改进1结构图: 改进2结构图: 改进3结构

【HTTPS】深入解析 https

我的主页&#xff1a;2的n次方_ 1. 背景介绍 在使用 http 协议的时候是不安全的&#xff0c;可能会出现运营商劫持等安全问题&#xff0c;运营商通过劫持 http 流量&#xff0c;篡改返回的网页内容&#xff0c;例如广告业务&#xff0c;可能会通过 Referer 字段 来统计是…

【Java】—— 泛型:泛型的理解及其在集合(List,Set)、比较器(Comparator)中的使用

目录 1. 泛型概述 1.1 生活中的例子 1.2 泛型的引入 2. 使用泛型举例 2.1 集合中使用泛型 2.1.1 举例 2.1.2 练习 2.2 比较器中使用泛型 2.2.1 举例 2.2.2 练习 1. 泛型概述 1.1 生活中的例子 举例1&#xff1a;中药店&#xff0c;每个抽屉外面贴着标签 举例2&…

图示详解OpenEuler下 DNS安装、配置与测试

前言 DNS配置内容、步骤、参数较多&#xff0c;初学者很难短时间掌握&#xff0c;另外&#xff0c;理解DNS工作原理也有一定的难度&#xff0c;一次配置成功的概率不大&#xff0c;因此&#xff0c;建议在配置DNS之前&#xff0c;先读一下之前笔者的博文《详解DNS工作原理及实…

Ubuntu24.04.1系统下VideoMamba环境配置

文章目录 前言第一步&#xff1a;基本的环境创建第二步&#xff1a;causal-conv1d和mamba_ssm库的安装第三步&#xff1a;安装requirements.txt 前言 VideoMamba环境的配置折磨了我三天&#xff0c;由于Mamba对Cuda的版本有要求&#xff0c;因此配置环境的时候Cuda版本以及各种…