设计模式10:观察者模式(订阅-发布)
系列总链接:《大话设计模式》学习记录_net 大话设计-CSDN博客
参考:简说设计模式——工厂方法模式 - JAdam - 博客园
参考:简单工厂模式(Simple Factory Pattern) - 回忆酿的甜 - 博客园
一:概述
观察者模式(Observer Pattern)是软件设计模式中的一种行为型模式。它定义了一种一对多的依赖关系,使得多个观察者对象同时监听一个主题对象(Subject)。当主题对象的状态发生改变时,所有依赖于它的观察者对象都会得到通知并自动更新。
想象一下你订阅了一份杂志,当你订阅了这份杂志之后,每当有新一期出版时,杂志社就会自动给你寄一份过来。在这个例子中,你是“观察者”,而杂志社是“主题”。
把这个概念应用到软件开发中:
主题(Subject):就像是杂志社,它持有一份订阅名单(即观察者的列表)。当主题的状态发生变化或发生了某些重要的事情时,它会通知所有在名单上的观察者。
观察者(Observer):就像你和其他订阅者一样,观察者是对主题感兴趣的对象。它们注册到主题上,表示愿意接收更新。一旦主题有了新的信息,观察者们就会收到通知并作出相应的反应。
具体来说,如果你是一个用户,想要知道某个网站是否有新产品发布,你可以选择订阅该网站的邮件提醒服务。这个网站就是“主题”,而你的电子邮件客户端就是“观察者”。当网站发布了新产品,它会通过发送邮件的方式通知你(以及其他订阅了相同服务的用户),这样你就知道了有新产品可以购买。
特点:
解耦:你不需要知道是谁在管理这些邮件提醒,同样地,网站也不需要知道谁订阅了它的服务。双方只需要遵循一定的协议(如提供邮箱地址、点击订阅按钮等)。
灵活性:你可以随时取消订阅或者重新订阅,这意味着你可以动态地加入或离开观察者名单。
广播式的通知:只要有一个新产品发布,所有的订阅者都会同时收到通知,而不是一个接一个地通知。
事件驱动:这种模式非常适合用于那些需要对特定事件做出响应的情况,比如点击按钮、数据变化等。
二:结构与实现
结构:
借用下网络上的图:
来源:【设计模式系列】--观察者模式_观察者设计模式-CSDN博客
大抵分为:
观察者(订阅者):想要观察订阅某些主题的用户;
主题:也可叫频道,用来将消息数据分类,供用户观察订阅;
主题代理(发布者):管理主题和观察者(订阅者)关系的,当主题需发送消息时,由其向订阅者发送消息;
说明:
一般只要有个订阅类,发布者类即可,主题是在发布者类中进行定义,一般可以直接是字符串什么的。
实现:
mainwindow.ui:
mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>//订阅者
class ISubscriber : public QObject
{Q_OBJECT
public:ISubscriber() {}virtual ~ISubscriber() {}virtual QString name() = 0;virtual void onMessage(QString channel, QString message) = 0;
};//发布者
class IPublisher : public QObject
{Q_OBJECT
public:IPublisher() {}virtual ~IPublisher() {}QHash<QString, QVector<ISubscriber*>> m_allSubscribers;void subscribe(QString channel, ISubscriber* subscriber){if(m_allSubscribers.contains(channel)){for(int i=0; i<m_allSubscribers.count(); i++){if(m_allSubscribers.value(channel).at(i) == subscriber){qDebug("Channel[%s] has been subscribed by subscriber[%s].",channel.toLatin1().data(), subscriber->name().toLatin1().data());return;}}m_allSubscribers[channel].append(subscriber);qDebug("Channel exist, Subscriber[%s] subscribe channel[%s].",subscriber->name().toLatin1().data(), channel.toLatin1().data());}else{QVector<ISubscriber*> vector;vector.append(subscriber);m_allSubscribers.insert(channel, vector);qDebug("Channel not exist, Subscriber[%s] subscribe channel[%s].",subscriber->name().toLatin1().data(), channel.toLatin1().data());}}void publish(QString channel, QString message){if(m_allSubscribers.contains(channel)){for(int i=0; i<m_allSubscribers.value(channel).count(); i++){m_allSubscribers.value(channel).at(i)->onMessage(channel, message);}}else{qDebug("Channel not exist, publiser channel[%s] message no receiver.",channel.toLatin1().data());}}void unSubscribe(QString channel, ISubscriber* subscriber){if(m_allSubscribers.contains(channel)){for(int i=0; i<m_allSubscribers.count(); i++){if(m_allSubscribers.value(channel).at(i) == subscriber){m_allSubscribers[channel].remove(i);qDebug("Remove subscriber[%s] of channel[%s].",subscriber->name().toLatin1().data(),channel.toLatin1().data());return;}}}else{qDebug("unSubscribe, Channel not exist.",channel.toLatin1().data());}}
};class MySubscriber : public ISubscriber
{Q_OBJECT
public:MySubscriber(QString name) : m_name(name) {}virtual ~MySubscriber() {}QString name(){return m_name;}void onMessage(QString channel, QString message){qDebug("%s,name:%s, channel:%s, message:%s",Q_FUNC_INFO, m_name.toLatin1().data(),channel.toLatin1().data(), message.toLatin1().data());}private:QString m_name;
};QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr);~MainWindow();private slots:void on_btn_sub1_clicked();void on_btn_sub2_clicked();void on_btn_pub_clicked();private:Ui::MainWindow *ui;MySubscriber* m_subscriber1;MySubscriber* m_subscriber2;IPublisher* m_publisher;
};
#endif // MAINWINDOW_H
mainwindow.cpp:
#include "MainWindow.h"
#include "ui_MainWindow.h"MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);m_subscriber1 = new MySubscriber("sub1");m_subscriber2 = new MySubscriber("sub2");m_publisher = new IPublisher;
}MainWindow::~MainWindow()
{delete ui;if(m_subscriber1){m_subscriber1->deleteLater();}if(m_subscriber2){m_subscriber2->deleteLater();}if(m_publisher){m_publisher->deleteLater();}
}void MainWindow::on_btn_sub1_clicked()
{if(!ui->lineEdit_sub1->text().isEmpty()){m_publisher->subscribe(ui->lineEdit_sub1->text(), m_subscriber1);}
}void MainWindow::on_btn_sub2_clicked()
{if(!ui->lineEdit_sub2->text().isEmpty()){m_publisher->subscribe(ui->lineEdit_sub2->text(), m_subscriber2);}
}void MainWindow::on_btn_pub_clicked()
{if(!ui->lineEdit_pub->text().isEmpty()){m_publisher->publish(ui->lineEdit_pub->text(), ui->lineEdit_message->text());}
}
三:应用
1.Redis的订阅发布机制;
2.手机新闻订阅哪些频道的新闻,该频道出了新消息会推送出来;
四:优缺点及适用环境
优点:
解耦合:主题和观察者之间低耦合,主题不需要知道观察者的具体实现。
灵活性:可以动态添加或移除观察者,系统更灵活。
广播通信:适合用于需要向多个对象同时发送通知的情况。
缺点:
性能问题:大量观察者可能导致通知过程效率低下。
复杂性增加:可能引入不必要的复杂性,特别是当更新链很长时。
依赖管理:如果不妥善管理,可能会造成内存泄漏或循环依赖。
适用环境:
1.当一个对象的状态变化需要影响其他多个对象时。
2.适用于事件驱动系统,如GUI事件处理、消息发布/订阅系统等。
3.需要保持对象间的一致性但又不想直接耦合时。
简而言之,观察者模式非常适合用于需要维护对象间松散耦合并且能够方便地添加或删除依赖关系的场景。
相关文章:
设计模式10:观察者模式(订阅-发布)
系列总链接:《大话设计模式》学习记录_net 大话设计-CSDN博客 参考:简说设计模式——工厂方法模式 - JAdam - 博客园 参考:简单工厂模式(Simple Factory Pattern) - 回忆酿的甜 - 博客园 一:概述 观察者模式࿰…...
2020 年 12 月青少年软编等考 C 语言四级真题解析
目录 T1. 开餐馆思路分析T2. 邮票收集思路分析T3. 带通配符的字符串匹配思路分析T4. 删除数字思路分析T1. 开餐馆 北大信息学院的同学小明毕业之后打算创业开餐馆。现在共有 n n n 个地点可供选择。小明打算从中选择合适的位置开设一些餐馆。这 n n n 个地点排列在同一条直线…...
高级java每日一道面试题-2024年12月03日-JVM篇-什么是Stop The World? 什么是OopMap? 什么是安全点?
如果有遗漏,评论区告诉我进行补充 面试官: 什么是Stop The World? 什么是OopMap? 什么是安全点? 我回答: 在Java虚拟机(JVM)中,Stop The World、OopMap 和 安全点 是与垃圾回收(GC)和性能优化密切相关的概念。理…...
探索 Apache Commons Collections 4:Java 集合框架的强大扩展
在 Java 开发中,集合框架是处理数据的核心工具。然而,标准 Java 集合框架虽然功能强大,但在某些场景下仍显得不够灵活。Apache Commons Collections 4(以下简称 commons-collections4)作为一个强大的工具库,…...
NIO(New IO)和BIO(Blocking IO)的区别
Java中的NIO(New IO)和BIO(Blocking IO)的区别及NIO的核心组件 Java中的NIO(New IO)和BIO(Blocking IO)是两种不同的网络通信模型,各自具有独特的特性和适用场景。下面将…...
【串口助手开发】visual studio 使用C#开发串口助手,生成在其他电脑上可执行文件,可运行的程序
1、改成Release,生成解决方案 串口助手调试成功后,将Debug改为Release,点击生成解决方案 2、运行exe文件 生成解决方案后,在bin文件夹下, Release文件夹下,生成相关文件 复制一整个Release文件夹…...
Linux 编译 convert_geotiff 时遇到的几个问题
步骤1:安装libgeotiff-dev 在ubuntu上,安装命令为: sudo apt-get install libgeotiff-dev在macos上,安装命令为: brew install libgeotiff在Linux上安装命令为: sudo yum install libgeotiff-devel注意…...
执行存储过程报:This function has none of DETERMINISTIC, NO SQL ???
执行存储过程时报如下错你该怎么整? [Err] 1418 - This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators variable)来…...
JAVA |日常开发中Servlet详解
JAVA |日常开发中Servlet详解 前言一、Servlet 概述1.1 定义1.2 历史背景 二、Servlet 的生命周期2.1 加载和实例化2.2 初始化(init 方法)2.3 服务(service 方法)2.4 销毁(destroy 方法) 三、Se…...
Spring Cloud Alibaba 之 “Sentinel”
从网上下载好sentinel-dashboard-1.6.3.jar,然后执行 java -jar sentinel-dashboard-1.6.3.jar,执行成功之后在浏览器输入localhost:8080,Sentinel的登录名和密码都是sentinel,登陆成功之后看到只有一个首页。 接下来开始整合Spring Cloud Alibaba Sen…...
UE4外挂实现分析-PC端-附源码
UE4外挂实现分析-PC端 游戏分析 分析工具: Cheat Engine 7.5 x64dbg IDA Pro 参考文章: UE4逆向笔记之GWORLD GName GameInstance - 小透明‘s Blog 【项目源码下载】https://download.csdn.net/download/Runnymmede/90079718 本次分析的游戏使用UE4.2…...
力扣88题:合并两个有序数组
力扣88题:合并两个有序数组 题目描述 给定两个按非递减顺序排列的整数数组 nums1 和 nums2,以及它们的长度 m 和 n,要求将 nums2 合并到 nums1,使得合并后的数组仍按非递减顺序排列。 输入与输出 示例 1: 输入&am…...
Lua面向对象实现
Lua中的面向对象是通过表(table)来模拟类实现的,通过setmetatable(table,metatable)方法,将一个表设置为当前表的元表,之后在调用当前表没有的方法或者键时,会再查询元表中的方法和键,以此来实现…...
小程序 模版与配置
WXML模版语法 一、数据绑定 1、数据绑定的基本原则 (1)在data中定义数据 (2)在WXML中使用数据 2、在data中定义页面的数据 3、Mustache语法的格式(双大括号) 4、Mustache语法的应用场景 (…...
【Elasticsearch】实现分布式系统日志高效追踪
🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/literature?__c1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,…...
探索Go语言中的循环单链表
简介 循环单链表是一种特殊的链表数据结构,它的最后一个节点指向链表的头节点,形成一个闭环。今天我们将探讨如何在Go语言中实现和操作这种数据结构。 为什么选择循环单链表? 连续访问:在循环单链表中,可以无限循环…...
[go-redis]客户端的创建与配置说明
创建redis client 使用go-redis库进行创建redis客户端比较简单,只需要调用redis.NewClient接口创建一个客户端 redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379",Password: "",DB: 0, })NewClient接口只接收一个参数red…...
Windows 和 Linux 系统命令行操作详解:从文件管理到进程监控
1.切换盘符与目录操作 在命令行中,切换盘符和目录是最常见的操作。尽管 DOS 和 Linux 在这些操作上有所不同,但它们都能实现相似的功能。 (1)切换盘符 ①DOS命令:在 DOS 中,切换盘符非常简单,使用 盘符名:ÿ…...
SpringBoot中@Import和@ImportResource和@PropertySource
1. Import Import注解是引入java类: 导入Configuration注解的配置类(4.2版本之前只可以导入配置类,4.2版本之后也可以导入普通类)导入ImportSelector的实现类导入ImportBeanDefinitionRegistrar的实现类 SpringBootApplication…...
etcd-v3.5release-(3)-readIndexRead
笔记1:读操作包括两种,readIndex和serilizable,readIndex指一致性读,一旦a读到了数据x,那么a及a以后的数据都能读到x,readIndex读会先确认本leader是不是有效地leader,如果有效则记录此刻的comm…...
Chrome 中小于 12px 文字的实现方式与应用场景详解
让 Chrome 支持小于 12px 的文字 在 Web 开发中,有时需要将文字显示为小于 12px 的尺寸,尤其是在设计精细的 UI 元素时。虽然大多数浏览器支持小于 12px 的字体大小,但 Chrome 默认情况下会通过调整文本渲染来确保文字可读性,尤其在非常小的文字尺寸下,可能会进行抗锯齿处…...
数据挖掘之数据预处理
引言 数据挖掘是从大量数据中提取有用信息和知识的过程。在这个过程中,数据预处理是不可或缺的关键步骤。数据预处理旨在清理和转换数据,以提高数据质量,从而为后续的数据挖掘任务奠定坚实的基础。由于现实世界中的数据通常…...
slam学习笔记6---样例展示雅可比手推过程
背景:一直在使用模板、自动化求导,对于背后雅可比推导只剩一个基本概念,有必要好好梳理巩固一下。本人水平有限,若推导过程有误,欢迎评论区提出。 假设一个二维slam问题,使用欧式距离观测模型,…...
ThreadLocal 详解
ThreadLocal 详解 ThreadLocal 是 Java 提供的一种线程本地存储机制,用于为每个线程提供独立的变量副本,变量的值仅在线程内可见,从而实现线程隔离。这种特性在需要为每个线程维护独立状态的场景中非常有用,例如用户上下文、事务…...
SQL SERVER 2016 AlwaysOn 无域集群+负载均衡搭建与简测
之前和很多群友聊天发现对2016的无域和负载均衡满心期待,毕竟可以简单搭建而且可以不适用第三方负载均衡器,SQL自己可以负载了。windows2016已经可以下载使用了,那么这回终于可以揭开令人憧憬向往的AlwaysOn2016 负载均衡集群的神秘面纱了。 …...
Unity 基于Collider 组件在3D 物体表面放置3D 物体
实现 从鼠标点击的屏幕位置发送射线,以射线监测点击到的物体,根据点击物体的法线向量调整放置物体的位置及朝向。 Ray ray Camera.main.ScreenPointToRay(Input.mousePosition); if (Physics.Raycast(ray, out RaycastHit hit, 100)) {obj.transform.…...
项目搭建:springboot,mybatis, maven
创建一个基于Spring Boot、MyBatis和Maven的项目可以简化很多配置,因为Spring Boot自带了很多自动配置的功能。下面我将给出一个简单的示例来展示如何搭建这样一个项目。 ### 1. 创建一个新的Spring Boot项目 你可以通过Spring Initializr(https://sta…...
网页端五子棋对战(四)---玩家匹配实现上线下线处理
文章目录 1.游戏大厅用户匹配1.1请求和响应1.2设计匹配页面1.3获取玩家信息1.4玩家信息的样式设置1.5初始化我们的websocket1.6点击按钮和客户端交互1.7点击按钮和服务器端交互 2.服务器端实现匹配功能框架2.1方法重写2.2借用session 3.处理上线下线3.1什么是上线下线3.2实现用…...
Linux笔记---进程:进程替换
1. 进程替换的概念 进程替换是指在一个正在运行的进程中,用一个新的程序替换当前进程的代码和数据,使得进程开始执行新的程序,而不是原来的程序。 这种技术通常用于在不创建新进程的情况下,改变进程的行为。 我们之前谈到过for…...
【AI日记】24.12.04 kaggle 比赛 Titanic-7
【AI论文解读】【AI知识点】【AI小项目】【AI战略思考】【AI日记】 工作 内容: 学习 kaggle 入门比赛 Titanic - Machine Learning from Disaster学习机器学习基础(pandas,numpy,sklearn,seaborn,matplotl…...
使用 Flutter 进行移动应用开发:深入探索
文章目录 前言一、介绍二、安装 Flutter 环境三、Flutter 应用结构与基础组件四、状态管理策略五、高级主题结语 前言 随着移动技术的迅猛发展,跨平台开发的需求日益增长。开发者们一直在寻找一种既能保证应用性能又能减少开发成本和时间的技术方案。Flutter 应运而…...
SQLServer 服务器只接受 TLS1.0,但是客户端给的是 TLS1.2
Caused by: javax.net.ssl.SSLHandshakeException: the server selected protocol version TLS10 is not accepted by client preferences [TLS12] 原因描述:SQLServer 服务器只接受 TLS1.0,但是客户端给的是 TLS1.2 解决方法如下: 打开文件…...
Linux命令行解释器的模拟实现
欢迎拜访:羑悻的小杀马特.-CSDN博客 本篇主题:Linux命令行解释器 制作日期:2024.12.04 隶属专栏:linux之旅 本篇简介: 主线带你用ubuntu版系统步步分析实现基础版本的shell;比如支持重定向操作࿰…...
模块化设计割草机器人系统研究与实现(系统架构师论文)
摘要: 割草机器人项目旨在实现自主草坪修剪,以提高园艺工作的效率。本文以具体项目为背景,介绍系统架构师在项目开发过程中的架构设计策略、关键技术应用、问题解决方案和优化措施。首先,本文分析割草机器人项目的总体架构需求&a…...
javascript(前端)作为客户端端通过grpc与cpp(服务端)交互
参考文章 https://blog.csdn.net/pathfinder1987/article/details/129188540 https://blog.csdn.net/qq_45634989/article/details/128151766 前言 临时让我写前端, 一些配置不太懂, 可能文章有多余的步骤但是好歹能跑起来吧 你需要提前准备 公司有自带的这些, 但是版本大都…...
股市复盘笔记
复盘是股市投资中非常重要的一个环节,它指的是投资者在股市收盘后,对当天的市场走势、个股表现以及自己的交易行为进行回顾和总结,以便更好地指导未来的投资决策。以下是对复盘的详细解释: 一、复盘的目的 总结市场走势ÿ…...
【SARL】单智能体强化学习(Single-Agent Reinforcement Learning)《纲要》
📢本篇文章是博主强化学习(RL)领域学习时,用于个人学习、研究或者欣赏使用,并基于博主对相关等领域的一些理解而记录的学习摘录和笔记,若有不当和侵权之处,指出后将会立即改正,还望谅…...
Linux 权限管理:用户分类、权限解读与常见问题剖析
🌟 快来参与讨论💬,点赞👍、收藏⭐、分享📤,共创活力社区。🌟 🚩用通俗易懂且不失专业性的文字,讲解计算机领域那些看似枯燥的知识点🚩 目录 💯L…...
Windows通过指令查看已安装的驱动
Windows通过指令查看已安装的驱动 在 Windows 操作系统中,有几种命令可以用来查看已安装的驱动程序。以下是常见的几种方法: 1. 使用 pnputil 查看已安装驱动程序 pnputil 是一个 Windows 内置工具,可以列出所有已安装的驱动程序包。 命令…...
Springboot(五十一)SpringBoot3整合Sentinel-nacos持久化策略
上文中我记录了在Springboot项目中链接sentinel-dashboard使用限流规则的全过程。 但是呢,有一个小小的问题,我重启了一下我本地的sentinel-dashboard服务,然后,我之前创建的所有的流控规则都没了…… 这……好像有点不合理啊,咱就不能找地儿存储一下?你这一重启就没了,…...
Scala的正则表达式
package hfdobject Test35_3 {def main(args: Array[String]): Unit {println("a\tb")//定义一个规则 正则表达式//1. .表示除了换行之外的其他的任意单个字符//2. \d等于[0-9] 匹配一个数字//3. \D除了\d之外的其他的任意字符,表示非数字//4. \w等价于[…...
cuda12.1版本的pytorch环境安装记录,并添加到jupyter和pycharm中
文章目录 前置准备使用anaconda prompt创建虚拟环境创建虚拟环境激活pytorch虚拟环境把pytorch下载到本地使用pip把安装包安装到pytorch环境中进入python环境检验是否安装成功将环境添加到jupyter在pycharm中使用该环境: 前置准备 安装anaconda,我的版本…...
牛客linux
1、 统计文件的行数 # 方法 1 wc -l ./nowcoder.txt | awk {print $1} # 方法 2 ,awk 可以打印所有行的行号, 或者只打印最后一行 awk {print NR} ./nowcoder.txt |tail -n 1 awk END{print NR} ./nowcoder.txt # 方法 3 grep -c 、-n等等 grep -c "" ./…...
通过HTML Canvas 在图片上绘制文字
目录 前言 一、HTML Canvas 简介 二、准备工作 三、绘制图片 四、绘制文字 五、完整代码 效果演示: 前言 HTML canvas 为我们提供了无限的创意可能性。今天,我们就来探索一下如何通过 HTML canvas 将图片和文字绘制到图片上,创造出独特…...
【C#】ListBox中找到多个image中的其中一个并重置赋值以便清理占用内存
1.ListBox中定义多个image 定义ListBox前台代码及Image控件的赋值 <ListBox Background"{DynamicResource BackgroundBrush}" ItemsSource"{Binding ElementNameDRFinish,PathImages}" Style"{x:Null}" Name"ImageList"ItemConta…...
Apache Commons工具类库使用整理
文章目录 Apache Commons工具类库分类- commons-lang3字符串工具:StringUtils日期工具:DateUtils数值工具:NumberUtils对象工具:ObjectUtils数组工具:ArrayUtils异常工具:ExceptionUtils枚举工具࿱…...
npm 设置镜像
要在npm中设置镜像,你可以使用npm config命令。以下是设置npm镜像的步骤: 临时使用淘宝镜像: npm --registry https://registry.npmmirror.com install package-name 永久设置镜像: npm config set registry https://registry…...
数据结构自测5
第6章 树和二叉树 自测卷解答 一、下面是有关二叉树的叙述,请判断正误(每小题1分,共10分) ( √ )1. 若二叉树用二叉链表作存贮结构,则在n个结点的二叉树链表中只有n—1个非空指针域。 ÿ…...
【后端面试总结】缓存策略选择
一般来说我们常见的缓存策略有三种,他们各自的优劣势和实现逻辑分别如下 Cache Aside(旁路缓存) 特点: 灵活性高:应用程序直接与缓存和数据库交互,具有高度的灵活性,可以根据业务需求自定义缓…...
40分钟学 Go 语言高并发:RPC服务开发实战
RPC服务开发实战 一、RPC服务基础概览 开发阶段关键点重要程度考虑因素接口设计API定义、协议选择、版本控制⭐⭐⭐⭐⭐可扩展性、兼容性服务实现业务逻辑、并发处理、资源管理⭐⭐⭐⭐⭐性能、可靠性错误处理异常捕获、错误码、故障恢复⭐⭐⭐⭐稳定性、可维护性性能测试负载…...