当前位置: 首页 > news >正文

Java二十三种设计模式-命令模式(18/23)

命令模式:将请求封装为对象的策略

概要

本文全面探讨了命令模式,从基础概念到实现细节,再到使用场景、优缺点分析,以及与其他设计模式的比较,并提供了最佳实践和替代方案,旨在帮助读者深入理解命令模式并在实际开发中有效应用。

 基础知识,java设计模式总体来说设计模式分为三大类:

(1)创建型模式,共5种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

(2)结构型模式,共7种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

(3)行为型模式,共11种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

第一部分:命令模式概述

1.1 定义与用途

命令模式(Command Pattern)是一种行为型设计模式,它将请求或操作封装成对象,允许用户使用不同的请求对客户进行参数化,并支持撤销操作。

命令模式是一种行为型设计模式,它将请求或操作封装为一个对象,这样你可以使用不同的请求、队列或日志请求来参数化其他对象,并支持撤销操作。

为何需要命令模式:

  • 解耦请求发送者与接收者:命令模式将请求发送者与接收者分离,提高系统的灵活性。
  • 支持撤销与重做:命令模式允许操作的撤销和重做,提高了用户体验。
  • 扩展性:允许系统在不修改现有代码的基础上扩展新命令。

1.2 命令模式的组成

命令(Command)

  • 定义:定义了执行操作的接口,包含执行命令的方法。
  • 职责:作为所有具体命令的抽象基类。

接收者(Receiver)

  • 定义:具体执行命令的对象,实现了命令接口中声明的操作。
  • 职责:接收命令请求并执行相应的操作。

调用者(Invoker)

  • 定义:要求命令对象执行请求。
  • 职责:维护命令对象的引用,并触发命令的执行。

客户端(Client)

  • 定义:创建具体的命令对象,并设置其接收者。
  • 职责:将调用者与命令对象关联,以便调用者可以执行命令。

角色之间的交互

  • 命令创建:客户端创建具体的命令对象,并将接收者传入命令对象。
  • 命令设置:调用者持有命令对象的引用,并在适当的时候调用命令的执行方法。
  • 命令执行:命令对象执行操作,接收者实际执行命令的细节。

命令模式通过将请求封装为对象,允许系统以统一的方式处理请求,同时支持撤销和重做操作。在下一部分中,我们将通过Java代码示例来展示命令模式的具体实现。

第二部分:命令模式的实现

2.1 Java实现示例

以下是使用Java语言实现命令模式的代码示例。假设我们有一个简单的文本编辑器,支持撤销和重做操作。

// 命令接口
interface Command {void execute();void undo();
}// 接收者:文本编辑器
class TextEditor {private String text;public TextEditor() {this.text = "";}public void setText(String text) {this.text = text;}public String getText() {return text;}
}// 具体命令:添加文本
class AppendTextCommand implements Command {private TextEditor editor;private String appendText;public AppendTextCommand(TextEditor editor, String appendText) {this.editor = editor;this.appendText = appendText;}@Overridepublic void execute() {editor.setText(editor.getText() + appendText);}@Overridepublic void undo() {int index = editor.getText().lastIndexOf(appendText);if (index >= 0) {editor.setText(editor.getText().substring(0, index));}}
}// 调用者:命令执行器
class CommandExecutor {private Command command;public void setCommand(Command command) {this.command = command;}public void executeCommand() {command.execute();}
}// 客户端代码
public class Client {public static void main(String[] args) {TextEditor editor = new TextEditor();CommandExecutor executor = new CommandExecutor();Command appendCmd = new AppendTextCommand(editor, "Hello World!");executor.setCommand(appendCmd);executor.executeCommand();System.out.println(editor.getText()); // 输出: Hello World!// 执行撤销操作appendCmd.undo();System.out.println(editor.getText()); // 输出: (空)}
}

2.2 命令模式中的角色和职责

命令(Command)

  • 职责:定义了执行操作的接口,允许将请求封装为一个对象。

接收者(Receiver)

  • 职责:具体执行命令中包含的操作,是命令要作用的对象。

调用者(Invoker)

  • 职责:要求命令对象执行请求,维护对命令对象的引用。

客户端(Client)

  • 职责:创建具体的命令对象,将接收者与命令关联,并提供给调用者。

相互作用

  • 命令创建:客户端创建具体的命令对象,指定命令的接收者和必要的参数。
  • 命令设置:调用者接收命令对象,并在适当的时候执行命令。
  • 命令执行:调用者调用命令对象的execute方法来执行操作,调用undo方法来撤销操作。

命令模式通过将请求封装为对象,允许系统以统一的方式处理请求,并支持撤销和重做操作。这种模式在需要对操作进行记录、撤销或重做的场景中非常有用。在下一部分中,我们将探讨命令模式的使用场景。

第三部分:命令模式的使用场景

3.1 需要将操作封装为对象的场景

在软件系统中,经常需要将操作封装为对象,以实现对操作的统一管理和调度。

讨论在需要将操作封装为对象时,命令模式的应用:

  • 操作的参数化:命令模式允许将操作的参数封装在命令对象中,使得操作可以在不同的时间点执行。
  • 操作的队列管理:通过命令对象,可以轻松地将操作加入队列、存储或序列化。
  • 接口的统一:命令模式提供了一个统一的操作执行接口,简化了调用者对操作的调用。

应用实例:

  • 任务调度系统:在任务调度系统中,命令模式可以将不同的任务封装为命令对象,统一调度和管理。
  • 图形界面操作:在图形用户界面中,用户的各种操作(如点击、拖拽)可以封装为命令对象,以支持撤销和重做功能。

3.2 需要支持撤销操作的场景

撤销操作是许多应用程序中的一个常见需求,特别是在文本编辑器、图形编辑器和其他需要用户交互的应用程序中。

分析在需要支持撤销操作时,命令模式的优势:

  • 撤销和重做支持:命令模式天然支持撤销操作,因为它将操作封装为对象,可以很容易地实现撤销和重做。
  • 操作的可逆性:通过命令对象的undo方法,可以轻松实现操作的逆向操作,满足用户撤销的需求。
  • 操作历史记录:命令模式可以与历史记录机制结合,存储操作序列,实现多级撤销和重做。

应用实例:

  • 文本编辑器:在文本编辑器中,用户的每次编辑操作都可以封装为一个命令对象,支持撤销和重做。
  • 游戏开发:在游戏开发中,玩家的操作可以封装为命令对象,以实现回放、撤销等功能。

命令模式通过将操作封装为对象,提供了一种灵活的方式来管理和调度操作,特别适用于需要撤销和重做功能的应用程序。在下一部分中,我们将讨论命令模式的优点与缺点。

第四部分:命令模式的优点与缺点

4.1 优点

解耦请求发送者与接收者

  • 灵活性增强:命令模式允许请求发送者和接收者之间没有直接联系,增加了系统的灵活性。

支持撤销操作

  • 操作可逆性:通过实现命令的undo()方法,命令模式支持撤销操作,提高了用户体验。

支持重做操作

  • 操作可重复性:在撤销操作之后,可以提供重做机制来恢复之前的状态。

支持事务性操作

  • 事务一致性:可以将一系列命令组合成一个事务,确保操作的一致性。

易于扩展

  • 开闭原则:遵循开闭原则,系统对扩展开放,对修改封闭,易于添加新命令。

增强安全性

  • 权限控制:可以对命令对象进行权限控制,限制对某些操作的访问。

4.2 缺点

增加系统复杂性

  • 类的数量:引入命令模式可能会增加系统中类的数量,每个命令都需要一个单独的类。

增加系统的开销

  • 性能问题:如果命令对象过多,可能会对性能产生影响。

难以管理命令依赖

  • 依赖关系:在复杂的系统中,命令之间的依赖关系可能难以管理。

难以实现跨系统命令

  • 系统边界:在分布式系统中,实现跨系统或跨网络的命令可能较为复杂。

可能引入循环依赖

  • 依赖循环:不当的使用可能导致命令对象之间的循环依赖。

撤销栈管理

  • 内存消耗:如果系统需要支持大量的撤销操作,撤销栈可能会消耗大量内存。

命令模式通过将请求封装为对象,提供了一种灵活的方式来管理和调度操作,支持撤销和重做功能。然而,它也需要谨慎使用,以避免增加系统的复杂性和维护难度。在实际应用中,根据具体需求和场景选择是否使用命令模式是非常重要的。在下一部分中,我们将比较命令模式与其他设计模式,并提供一些最佳实践和建议。

第五部分:命令模式与其他模式的比较

5.1 与策略模式的比较

策略模式

  • 定义:策略模式定义了一系列的算法,并将每一个算法封装起来,使它们可以互换。
  • 特点:策略模式关注于算法的封装和替换,通常用于多种算法或行为的动态选择。

命令模式

  • 定义:命令模式将请求或操作封装为一个对象,允许用户使用不同的请求对客户进行参数化。
  • 特点:命令模式关注于请求的封装,支持撤销和重做操作,以及请求的排队和记录。

对比

  • 封装内容:策略模式封装的是算法或行为,命令模式封装的是请求或操作。
  • 使用场景:策略模式适用于需要根据不同条件选择不同算法的场景,命令模式适用于需要对请求进行参数化处理的场景。

5.2 与观察者模式的对比

观察者模式

  • 定义:观察者模式定义了对象之间的一对多依赖关系,当一个对象状态发生改变时,所有依赖于它的对象都会得到通知。
  • 特点:观察者模式关注于对象状态变化的广播机制,允许对象在状态变化时通知多个观察者。

命令模式

  • 定义:如前所述,命令模式关注于请求的封装和执行。

对比

  • 通信机制:观察者模式是一种对象间的通信机制,命令模式是一种请求的封装和执行机制。
  • 目的:观察者模式用于实现对象间的状态同步,命令模式用于将请求作为对象进行处理。

命令模式和策略模式、观察者模式都提供了处理对象间交互的不同方法。每种模式都有其独特的用途和优势,选择使用哪种模式取决于具体的设计需求和场景。在下一部分中,我们将提供命令模式的最佳实践和建议。

第六部分:命令模式的最佳实践和建议

6.1 最佳实践

确保命令的线程安全

  • 线程安全:确保命令对象在多线程环境中使用时是安全的,避免并发问题。

明确命令的撤销逻辑

  • 可逆操作:为每个命令实现清晰的撤销逻辑,确保undo()方法能够正确地撤销执行的操作。

使用宏命令简化复杂序列

  • 宏命令:当需要执行一系列命令时,使用宏命令来简化操作,作为一个整体进行撤销和重做。

定义清晰的命令执行和撤销接口

  • 接口规范:定义清晰的接口,如execute()undo(),确保所有命令遵循相同的规范。

考虑命令的持久化

  • 持久化存储:如果需要,实现命令的持久化机制,以便在系统重启后恢复命令状态。

保持命令的轻量级

  • 轻量设计:设计命令时应尽量保持轻量,避免命令对象过于复杂或庞大。

6.2 避免滥用

避免过度使用命令模式

  • 适用场景:只在确实需要将请求作为对象处理,或者需要撤销和重做功能时使用命令模式。

避免命令对象过于复杂

  • 简化设计:避免命令对象包含过多的逻辑,保持其职责单一。

避免滥用撤销和重做功能

  • 合理使用:不应过度依赖撤销和重做功能,这可能导致用户滥用,从而影响用户体验。

6.3 替代方案

使用函数式编程技术

  • 函数作为命令:在支持函数式编程的语言中,可以使用函数或函数式接口作为命令。

使用事件驱动模型

  • 事件作为命令:在事件驱动的系统中,可以使用事件来代替命令模式处理请求。

使用状态模式

  • 状态封装:当对象状态变化复杂时,可以使用状态模式来封装状态相关的操作。

使用策略模式

  • 算法封装:如果主要需求是根据不同条件选择不同的算法或行为,策略模式可能是更好的选择。

命令模式是一种强大的设计模式,用于将请求封装为对象,支持撤销和重做操作。合理使用命令模式并避免其缺点对于构建灵活、可维护的系统至关重要。了解其替代方案可以帮助开发者根据具体需求和场景选择最合适的设计模式。在实际开发中,应根据具体情况灵活运用命令模式,以达到最佳的设计效果。

结语

命令模式提供了一种强大的方法来封装操作,允许系统以不同的方式处理请求,并支持撤销操作。通过本文的深入分析,希望读者能够对命令模式有更全面的理解,并在实际开发中做出合理的设计选择。


博主还写了其他Java设计模式关联文章,请各位大佬批评指正:

(一)创建型模式(5种):

Java二十三种设计模式-单例模式(1/23)

Java二十三种设计模式-工厂方法模式(2/23)

Java二十三种设计模式-抽象工厂模式(3/23)

Java二十三种设计模式-建造者模式(4/23)

Java二十三种设计模式-原型模式(5/23)

(二)结构型模式(7种): 

Java二十三种设计模式-适配器模式(6/23)

Java二十三种设计模式-装饰器模式(7/23)

Java二十三种设计模式-代理模式(8/23)

Java二十三种设计模式-外观模式(9/23)

Java二十三种设计模式-桥接模式(10/23)

Java二十三种设计模式-组合模式(11/23)

Java二十三种设计模式-享元模式(12/23)

 (三)行为型模式(11种): 

Java二十三种设计模式-策略模式(13/23)

Java二十三种设计模式-模板方法模式(14/23)

Java二十三种设计模式-观察者模式(15/23)

Java二十三种设计模式-迭代子模式(16/23)

Java二十三种设计模式-责任链模式(17/23)

持续更新中......敬请关注

相关文章:

Java二十三种设计模式-命令模式(18/23)

命令模式:将请求封装为对象的策略 概要 本文全面探讨了命令模式,从基础概念到实现细节,再到使用场景、优缺点分析,以及与其他设计模式的比较,并提供了最佳实践和替代方案,旨在帮助读者深入理解命令模式并…...

Kafka系列之:Dead Letter Queue死信队列DLQ

Kafka系列之:Dead Letter Queue死信队列DLQ 一、死信队列二、参数errors.tolerance三、创建死信队列主题四、在启用安全性的情况下使用死信队列更多内容请阅读博主这篇博客: Kafka系列之:Kafka Connect深入探讨 - 错误处理和死信队列一、死信队列 死信队列(DLQ)仅适用于接…...

Fragment学习笔记

静态加载 <fragment android:name"com.example.serviceapplication.fragment.TestFragment"android:layout_width"match_parent"android:layout_height"wrap_content"app:layout_constraintStart_toStartOf"parent"app:layout_cons…...

NGINX 基础参数与功能

章节 1 NGINX 的源码安装 2 NGINX 核心配置详解 3 NGINX 之 location 匹配优先级 4 NGINX 基础参数与功能 目录 1 实现 Nginx 账户认证功能 1.1 创建htpasswd 认证文件 1.2 创建数据目录 1.3 指定认证文件路径 1.4 测试效果 2 定义重定向错误日志 2.1 指定错误日志访问路…...

css设置元素居中显示

CSS中实现居中显示可以通过不同的属性来实现&#xff0c;取决于你是要水平居中还是垂直居中&#xff0c;或者两者都要。以下是一些常用的居中方法&#xff1a; 1.水平居中 - 行内元素或文本 .center-text {text-align: center; } 2.水平居中 - 块级元素 .center-block {mar…...

js判断一个任意值为空包括数组和对象

在JavaScript中&#xff0c;判断一个变量是否为空可以考虑以下几种情况&#xff1a; 如果变量可能是null或undefined&#xff0c;可以直接判断。 对于数组&#xff0c;如果想要判断数组为空&#xff08;长度为0&#xff09;&#xff0c;可以检查其length属性。 对于对象&…...

EmguCV学习笔记 VB.Net和C# 下的OpenCv开发

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 笔者的博客网址&#xff1a;https://blog.csdn.net/uruseibest 本教程将分为VB.Net和C#两个版本分别进行发布。 教程VB.net版本请…...

“TCP粘包”不是TCP的问题!

前言 写RPC用了Netty。涉及到粘包拆包问题。想复习一下。发现网上博客多是概念模糊不清。没有触及本质或者没有讲清楚。 遂决定自己写一篇 “TCP粘包”是谁的问题&#xff1f; 首先我们要明确TCP是面向字节流的协议。也就是说我们在应用层想使用TCP来传输数据时&#xff0c;…...

Electron项目依赖管理:最佳实践与常见错误

问题一 问题描述: 输入命令 pnpm add electron 后&#xff0c; electron 包在执行 postinstall 脚本时&#xff0c;尝试从网络上下载 Electron 二进制文件&#xff0c;但由于网络问题&#xff08;如连接超时或代理设置问题&#xff09;&#xff0c;导致下载失败。 λ pnpm a…...

华为数通路由交换HCIP/HCNP

2017-2022年软考高级网络规划设计师真题解析视频&#xff01;软考复习一定要多做历年真题&#xff01; 2022年软考网络规划设计师真题解析_哔哩哔哩_bilibili 2024年5月软考网络工程师真题解析合集&#xff0c;考后估分版【综合知识案例分析】 2024年5月软考网络工程师真题解…...

搜索面试题

1、目前怎么构建样本的&#xff1f;如果排序中第5个被点了&#xff0c;前面的作为负样本&#xff0c;后面的不要怎么样&#xff1b;为什么不好&#xff0c;为什么好。 点击作为负样本&#xff0c;曝光未点击作为负样本&#xff1b; 可以这样理解。您提到的排序中第5个被点的对…...

WPF学习(8) --Windows API函数的使用

一、API函数的介绍 1.FindWindow函数 [DllImport("user32.dll", CharSet CharSet.Auto)]public static extern IntPtr FindWindow(string lpClassName, string lpWindowName); 功能: FindWindow函数用于根据窗口的类名和窗口名称查找窗口的句柄&#xff08;IntPtr…...

Linux系统-用户账号文件

文章目录 文件一&#xff08;passwd&#xff09; 文件二&#xff08;shadow&#xff09; 加密密码部分 举例理解 文件三&#xff08;gshadow&#xff09; 文件四&#xff08;group&#xff09; 文件五&#xff08;skel&#xff09; 文件六&#xff08;login.defs&#…...

docker配置国内镜像加速

docker配置国内镜像加速 由于国内使用docker拉取镜像时&#xff0c;会经常出现连接超时的网络问题&#xff0c;所以配置Docker 加速来使用国内 的镜像加速服务&#xff0c;以提高拉取 Docker 镜像的速度。 1、备份docker配置文件 cp /etc/docker/daemon.json /etc/docker/da…...

C语言实现排序之堆排序算法

一、堆排序算法 基本思想 堆排序是一种比较有效的排序方法&#xff0c;其基本思想是&#xff1a; 构建最大堆&#xff1a;首先将待排序的数组构建成一个最大堆&#xff0c;即对于每个非叶子节点&#xff0c;它的值都大于或等于其子节点的值。排序&#xff1a;然后将堆顶元素…...

【STM32 Blue Pill编程】-外部中断配置及使用

外部中断配置及使用 文章目录 外部中断配置及使用1、中断介绍2、STM32中的中断3、硬件准备及接线4、GPIO配置5、代码实现在本文中,我们将介绍如何使用 STM32Cube IDE 中的 HAL 库配置和处理外部中断。 我们将通过一个带有按钮和 LED 的示例来演示这一点。 读完本文后,您将能够…...

MySQL 安装与配置教程:单机、主从复制与集群模式

目录 MySQL 简介MySQL 安装MySQL 基础配置MySQL 主从复制配置MySQL 集群配置总结 1. MySQL 简介 MySQL 是一个广泛使用的关系型数据库管理系统&#xff0c;具有高性能、高可靠性和易用性等特点。它支持多种部署模式&#xff0c;包括单机模式、主从复制模式&#xff08;用于高…...

JavaEE 的相关知识点(一)

一、过滤器 过滤器&#xff08;Filter&#xff09;是一个用于对请求和响应进行预处理的组件。过滤器可以在 Java Servlet 规范中使用&#xff0c;通常用于执行一些通用的任务 1、过滤器的作用 过滤器是一种javaEE规范中定义的一种技术&#xff0c;可以让请求达到目标servlet之…...

使用Python实现深度学习模型:智能医疗影像识别与诊断

介绍 智能医疗影像识别与诊断是现代医疗技术的重要应用,通过深度学习模型,可以自动分析和识别医疗影像,提高诊断的准确性和效率。本文将介绍如何使用Python和深度学习技术来实现智能医疗影像识别与诊断。 环境准备 首先,我们需要安装一些必要的Python库: pip install …...

24.给定一个链表,实现一个算法交换每两个相邻节点并返回其头部。要求不能修改列表节点中的值,只能更改节点本身。

24. Swap Nodes in Pairs 题目 给定一个链表,交换每两个相邻节点并返回其头部。要求不能修改列表节点中的值,只能更改节点本身。 Example: Given 1->2->3->4, you should return the list as 2->1->4->3....

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻

在如今就业市场竞争日益激烈的背景下&#xff0c;越来越多的求职者将目光投向了日本及中日双语岗位。但是&#xff0c;一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧&#xff1f;面对生疏的日语交流环境&#xff0c;即便提前恶补了…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:

一、属性动画概述NETX 作用&#xff1a;实现组件通用属性的渐变过渡效果&#xff0c;提升用户体验。支持属性&#xff1a;width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项&#xff1a; 布局类属性&#xff08;如宽高&#xff09;变化时&#…...

centos 7 部署awstats 网站访问检测

一、基础环境准备&#xff08;两种安装方式都要做&#xff09; bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats&#xff0…...

数据链路层的主要功能是什么

数据链路层&#xff08;OSI模型第2层&#xff09;的核心功能是在相邻网络节点&#xff08;如交换机、主机&#xff09;间提供可靠的数据帧传输服务&#xff0c;主要职责包括&#xff1a; &#x1f511; 核心功能详解&#xff1a; 帧封装与解封装 封装&#xff1a; 将网络层下发…...

【AI学习】三、AI算法中的向量

在人工智能&#xff08;AI&#xff09;算法中&#xff0c;向量&#xff08;Vector&#xff09;是一种将现实世界中的数据&#xff08;如图像、文本、音频等&#xff09;转化为计算机可处理的数值型特征表示的工具。它是连接人类认知&#xff08;如语义、视觉特征&#xff09;与…...

企业如何增强终端安全?

在数字化转型加速的今天&#xff0c;企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机&#xff0c;到工厂里的物联网设备、智能传感器&#xff0c;这些终端构成了企业与外部世界连接的 “神经末梢”。然而&#xff0c;随着远程办公的常态化和设备接入的爆炸式…...

Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档&#xff09;&#xff0c;如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下&#xff0c;风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...

JavaScript基础-API 和 Web API

在学习JavaScript的过程中&#xff0c;理解API&#xff08;应用程序接口&#xff09;和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能&#xff0c;使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...

【Linux】Linux 系统默认的目录及作用说明

博主介绍&#xff1a;✌全网粉丝23W&#xff0c;CSDN博客专家、Java领域优质创作者&#xff0c;掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围&#xff1a;SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...

CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝

目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为&#xff1a;一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...