【再谈设计模式】命令模式~封装请求的指挥者

ops/2025/2/7 18:11:56/

一、引言

        在软件工程,软件开发过程中,高效的设计模式是构建灵活、可维护和可扩展系统的关键。命令模式作为一种行为型设计模式,为处理各种操作请求提供了一种巧妙的解决方案。它像是一个指挥中心,有条不紊地协调请求的发送者和执行者之间的交互,使得系统在面对复杂的业务逻辑时能够保持清晰的结构。

二、定义与描述

命令模式的核心是将请求封装成独立的对象,也就是命令对象。这个模式包含四个主要角色:

  • 命令(Command):这是一个抽象的概念,通常定义为一个接口或者抽象类,其中声明了执行操作的方法,如execute()方法。
  • 具体命令(ConcreteCommand):实现了命令接口,内部包含一个接收者对象,并在execute()方法中调用接收者的相关操作。
  • 接收者(Receiver):负责执行具体的任务,是实际执行操作的对象。
  • 调用者(Invoker):负责调用命令对象的执行方法,可以持有多个命令对象,并在合适的时候触发命令的执行。

三、抽象背景

        在很多软件项目中,操作请求的管理是一个挑战。例如,在一个大型的企业级应用中,用户界面的各种操作(如点击按钮、选择菜单等)对应着不同的业务逻辑处理。如果没有良好的设计模式,这些操作和业务逻辑会紧密耦合在一起,导致代码难以理解、维护和扩展。命令模式通过将操作请求抽象成命令对象,有效地将操作的触发与实际执行分离开来,提高了系统的灵活性。

 

四、适用场景与现实问题解决

用户界面操作处理

        在图形界面应用中,用户通过菜单或按钮触发各种操作。使用命令模式,可以将每个操作封装为一个命令对象,使得界面代码和业务逻辑代码解耦。例如,在图像编辑软件中,“旋转图像”、“调整色彩”等操作可以分别被封装为不同的命令对象。这样,当界面布局发生变化或者需要添加新的操作时,只需要对命令对象和调用者进行调整,而不需要修改具体的图像处理逻辑。

事务处理与日志记录

        在数据库操作中,多个相关的操作需要作为一个事务来处理。命令模式可以将每个数据库操作封装成命令对象,然后通过一个事务管理器(调用者)来统一管理这些命令的执行。同时,可以方便地记录每个命令的执行情况,以便进行日志记录和错误恢复。例如,在一个转账系统中,“扣除转账金额”和“增加收款金额”这两个操作可以被封装为命令对象,由事务管理器来确保这两个操作要么都成功,要么都失败。

宏命令与批处理

        有时候需要将多个操作组合成一个宏命令或者进行批处理。命令模式可以轻松实现这一点,只需创建一个新的命令对象,在其execute()方法中按顺序调用其他命令对象的execute()方法即可。例如,在办公软件中,可以将“打开文件”、“设置页面格式”和“打印”等操作组合成一个宏命令,用户只需触发一次这个宏命令,就可以完成一系列的操作。

应用场景操作示例命令对象调用者业务逻辑或相关操作
图像编辑软件(图形界面应用)旋转图像、调整色彩旋转图像命令对象、调整色彩命令对象界面操作管理(假设)具体的图像旋转、色彩调整算法
转账系统(数据库事务处理)扣除转账金额、增加收款金额扣除金额命令对象、增加金额命令对象事务管理器数据库中金额的更新操作
办公软件(宏命令与批处理)打开文件、设置页面格式、打印打开文件命令对象、设置页面格式命令对象、打印命令对象宏命令执行管理(假设)对应的文件打开、页面格式设置、打印操作

五、命令模式的现实生活的例子

        想象一个智能家居系统。你有一个智能遥控器(调用者),可以控制多个智能设备(接收者),如智能灯、智能空调等。每个操作(如开灯、关灯、调整空调温度等)都可以看作是一个命令。例如,“开灯”这个命令(具体命令对象)包含了对智能灯(接收者)的操作指令。当你按下遥控器上的开灯按钮时,遥控器就会调用“开灯”这个命令对象的execute()方法,从而实现开灯的操作。

六、初衷与问题解决

初衷:

  • 解耦请求的发送者和接收者,使得两者可以独立变化,提高系统的灵活性和可维护性。
  • 方便对操作请求进行管理,如排队、记录日志、支持撤销/重做等功能。

问题解决:

  • 在复杂的软件系统中,有效地分离了界面操作与业务逻辑,避免了两者的高度耦合。
  • 对于需要事务处理和日志记录的系统,提供了一种简单而有效的方式来管理操作的执行顺序和记录执行情况。
  • 为实现撤销/重做功能提供了基础,通过维护命令对象的历史记录,可以方便地回滚或重复之前的操作。

七、代码示例

Java

类图:

// 接收者
class Receiver {public void doSomething() {System.out.println("执行接收者的操作");}
}// 命令接口
interface Command {void execute();
}// 具体命令
class ConcreteCommand implements Command {private Receiver receiver;public ConcreteCommand(Receiver receiver) {this.receiver = receiver;}@Overridepublic void execute() {receiver.doSomething();}
}// 调用者
class Invoker {private Command command;public void setCommand(Command command) {this.command = command;}public void call() {command.execute();}
}// 测试
public class CommandPatternJava {public static void main(String[] args) {Receiver receiver = new Receiver();Command command = new ConcreteCommand(receiver);Invoker invoker = new Invoker();invoker.setCommand(command);invoker.call();}
}

    流程图:

    时序图:

     

    C++

    #include <iostream>// 接收者
    class Receiver {
    public:void doSomething() {std::cout << "执行接收者的操作" << std::endl;}
    };// 命令抽象类
    class Command {
    public:virtual void execute() = 0;
    };// 具体命令
    class ConcreteCommand : public Command {
    private:Receiver* receiver;
    public:ConcreteCommand(Receiver* receiver) : receiver(receiver) {}void execute() override {receiver->doSomething();}
    };// 调用者
    class Invoker {
    private:Command* command;
    public:void setCommand(Command* command) {this.command = command;}void call() {command->execute();}
    };// 测试
    int main() {Receiver* receiver = new Receiver();Command* command = new ConcreteCommand(receiver);Invoker* invoker = new Invoker();invoker->setCommand(command);invoker->call();delete receiver;delete command;delete invoker;return 0;
    }

      Python

      # 接收者
      class Receiver:def do_something(self):print("执行接收者的操作")# 命令抽象类
      class Command:def execute(self):pass# 具体命令
      class ConcreteCommand(Command):def __init__(self, receiver):self.receiver = receiverdef execute(self):self.receiver.do_something()# 调用者
      class Invoker:def __init__(self):self.command = Nonedef set_command(self, command):self.command = commanddef call(self):if self.command:self.command.execute()# 测试
      receiver = Receiver()
      command = ConcreteCommand(receiver)
      invoker = Invoker()
      invoker.set_command(command)
      invoker.call()

      Go

      package mainimport "fmt"// 接收者
      type Receiver struct{}func (r *Receiver) doSomething() {fmt.Println("执行接收者的操作")
      }// 命令接口
      type Command interface {execute()
      }// 具体命令
      type ConcreteCommand struct {receiver *Receiver
      }func (c *ConcreteCommand) execute() {c.receiver.doSomething()
      }// 调用者
      type Invoker struct {command Command
      }func (i *Invoker) setCommand(command Command) {i.command = command
      }func (i *Invoker) call() {if i.command!= nil {i.command.execute()}
      }func main() {receiver := &Receiver{}command := &ConcreteCommand{receiver: receiver}invoker := &Invoker{}invoker.setCommand(command)invoker.call()
      }

      八、命令模式的优缺点

      优点

      • 解耦性高:发送者和接收者之间的耦合度大大降低,两者可以独立发展,提高了系统的可维护性和扩展性。
      • 可扩展性好:可以很容易地添加新的命令,只需创建新的具体命令类即可,不需要修改调用者和接收者的代码。
      • 方便管理操作请求:可以对命令进行排队、记录日志、支持撤销/重做等操作,便于对操作流程进行管理。

      缺点

      • 类的数量可能增加:每一个命令都需要一个具体的命令类,如果命令种类繁多,会导致类的数量过多,增加系统的复杂性。
      • 可能存在性能损耗:由于增加了命令对象这一中间层,在一些对性能要求极高的场景下,可能会有一定的性能损耗。
      特性描述
      优点
      解耦性高发送者和接收者之间耦合度降低,两者可独立发展,提升系统可维护性与扩展性。
      可扩展性好新增命令只需创建新的具体命令类,无需修改调用者和接收者代码。
      方便管理操作请求可对命令排队、日志记录、支持撤销/重做,便于操作流程管理。
      缺点
      类的数量可能增加每个命令需一个具体命令类,命令繁多时类数量过多,增加系统复杂性。
      可能存在性能损耗命令对象作为中间层,在高性能要求场景下可能有性能损耗。

      九、命令模式的升级版

      • 链式命令模式
        • 传统的命令模式中,调用者需要明确地设置要执行的命令对象。而在链式命令模式中,可以将多个命令对象连接成一个链,调用者只需要触发这个链的执行即可。例如,在一个文件处理系统中,可以有“打开文件”、“加密文件”、“保存文件”等命令,这些命令可以连接成一个链,当用户触发这个链时,文件会依次经过这些操作。
      • 命令模式
        • 宏命令是一种组合命令,它可以包含多个子命令。升级版的命令模式可以更好地支持宏命令的创建和执行。例如,在办公软件中,可以创建一个“文档排版宏命令”,这个宏命令内部包含“设置字体”、“调整段落格式”、“插入页码”等多个子命令。当用户执行这个宏命令时,所有的子命令会按照预定的顺序依次执行。
      • 异步命令模式
        • 在现代的软件系统中,异步操作越来越常见。升级版的命令模式可以更好地适应异步操作的需求。例如,在网络应用中,发送网络请求可以被封装成一个命令对象,这个命令对象可以异步地执行,并且在执行完成后可以通过回调函数通知调用者结果。这样可以提高系统的并发处理能力,提高用户体验。

       


      http://www.ppmy.cn/ops/156497.html

      相关文章

      物联网 STM32【源代码形式-使用以太网】连接OneNet IOT从云产品开发到底层MQTT实现,APP控制 【保姆级零基础搭建】

      物联网&#xff08;IoT&#xff09;‌是指通过各种信息传感器、射频识别技术、全球定位系统、红外感应器等装置与技术&#xff0c;实时采集并连接任何需要监控、连接、互动的物体或过程&#xff0c;实现对物品和过程的智能化感知、识别和管理。物联网的核心功能包括数据采集与监…

      unity学习28:灯光light相关 类型type,模式mode等

      目录 1 场景里的默认灯光 1.1 默认灯光 1.2 可以添加多个灯光 2 灯光类型 2.0 灯光的类型 2.1 定向灯光 directional light&#xff08;想象成太阳&#xff09; 2.1.1 全局和局部 2.2 聚光 spot ---- 类手电筒的光线 2.2.1 控制效果 2.2.2 还可以控制 spot light的…

      C# 程序计算圆的面积(Program to find area of a circle)

      给定圆的半径&#xff0c;求该圆的面积。 可以使用以下公式简单地计算圆的面积。 其中 r 是圆的半径&#xff0c;它可能是浮点数&#xff0c;因为饼图的值为 3.14 方法&#xff1a;使用给定的半径&#xff0c;使用上述公式找到面积&#xff1a;&#xff08;pi * r * r&#…

      无人机飞手光伏吊运、电力巡检、农林植保技术详解

      无人机飞手在光伏吊运、电力巡检、农林植保等领域的技术应用&#xff0c;体现了无人机技术的广泛性和实用性。以下是对这三个领域技术的详细解析&#xff1a; 一、无人机飞手光伏吊运技术 1. 技术背景 光伏发电站作为可再生能源的重要组成部分&#xff0c;其建设和维护对效率…

      基于Kamailio、MySQL、Redis、Gin、Vue.js的微服务架构

      每个服务使用一台独立的服务器的可行部署方案&#xff0c;尤其是在高并发、高可用性要求较高的场景中。这种方案通常被称为分布式部署或微服务架构。以下是针对您的VoIP管理系统&#xff08;基于Kamailio、MySQL、Redis、Gin、Vue.js&#xff09;的详细分析和建议。 1. 分布式部…

      MySQL —— 事务

      概念 事务把组SQL语句打包成为个整体&#xff0c;在这组SQL的执行过程中&#xff0c;要么全部成功&#xff0c;要么全部失败。 这组SQL语句可以是条也可以是多条。 ACID 特性 原子性 Atomicity(原子性)&#xff1a;一个事务中的所有操作&#xff0c;要么全部成功&#xff0…

      【测试用例翔实 栈】P8815 [CSP-J 2022] 逻辑表达式

      本文涉及知识点 C栈 [CSP-J 2022] 逻辑表达式 题目描述 逻辑表达式是计算机科学中的重要概念和工具&#xff0c;包含逻辑值、逻辑运算、逻辑运算优先级等内容。 在一个逻辑表达式中&#xff0c;元素的值只有两种可能&#xff1a; 0 0 0&#xff08;表示假&#xff09;和 …

      文档解析技术:如何高效提取PDF扫描件中的文字与表格信息?

      想要高效提取PDF扫描件中的文字与表格信息&#xff0c;通常需要借助专业的工具或在线服务&#xff0c;以下是一些可行的方法&#xff1a; 预处理扫描件&#xff1a;在提取文字之前&#xff0c;尽量确保扫描件的图像质量清晰。如果扫描件模糊或有污渍&#xff0c;可以使用图像处…