【面向接口编程(IOP)典型场景】底层组件如何实现回调通知上层应用系统? 另外一种实现方式

news/2025/9/21 12:02:24/文章来源:https://www.cnblogs.com/tortelee/p/19103377

偶然看到一篇文章, https://www.cnblogs.com/buguge/p/19055703
对这篇文章的设计进行了更改。

原来设计的类图 和流程图 :

deepseek_mermaid_20250921_877646

deepseek_mermaid_20250921_bb29b5

原有设计的优缺点:

优点
实现了解耦

  • 核心优势:业务层(插件)不依赖于上层应用的具体实现,而是依赖于一个抽象的接口 (TransferCallback)。这符合依赖倒置原则 (DIP),是插件化架构的基石。

  • 灵活性:上层应用可以自由决定是否关心回调事件(通过选择是否实现接口并注入),以及如何响应事件(在 onTransferSuccess 方法中实现任何业务逻辑)。

对应用层封装了异步复杂性

异步线程的创建、管理和任务提交完全由业务层(AccountTransferService)内部完成。上层应用只需调用一个同步风格的 accounting() 方法,无需关心其内部是多线程实现的,简化了调用方的使用。

“一站式”服务

对于上层应用来说,它只需要与 AccountTransferService 打交道。它既通过这个服务执行了转账操作,也通过它获得了操作完成的通知。如果不需要通知,甚至不需要感知 TransferCallback 接口的存在。这在简单场景下显得非常简洁。

缺点
破坏了单一职责原则 (SRP)

  • 核心问题:AccountTransferService 承担了过多的职责。它不仅是“转账业务”的执行者,还是“异步任务”的管理者,同时又是“事件”的发布者。这导致其职责不纯粹、不单一,变得臃肿,违反了高内聚的设计目标。

隐藏的控制流和调试困难

  • 回调函数使得程序的执行流程变得不直观。当阅读 AccountTransferService 的代码时,无法直接看出 transferCallback.onTransferSuccess() 最终会执行哪段代码,必须去查找被注入的具体实现是什么。

  • 当回调函数中出现 bug 时,栈追踪信息会分散在两个不同的线程和模块中,增加了定位问题的难度。

有限的灵活性和可复用性

  • 强绑定通知机制:业务层强制规定了通知的机制(同步回调)。如果上层应用希望用事件总线、消息队列等其他方式来处理通知,将无法实现,除非修改业务层代码。

  • 无法选择同步执行:业务层硬编码了异步逻辑。如果某个场景下希望同步等待转账完全完成后再继续,这个设计无法满足。

  • 复用性差:如果想在另一个不需要通知的项目中复用 AccountTransferService,会发现它依然拖着异步线程池和回调检查的逻辑,无法作为一个纯净的业务组件使用。

生命周期和并发管理复杂

  • 业务层需要自己管理线程池 (ExecutorService) 的生命周期(创建、关闭),这增加了其复杂度。

  • 在回调应用中,需要考虑线程安全问题。如果多个线程可能同时修改回调函数引用(通过 setter),需要做同步处理。

错误处理机制不完善

  • 原始设计只考虑了成功回调 (onTransferSuccess),没有定义失败回调。一个健壮的系统必须考虑异步操作可能失败的情况,而原有设计在这方面是缺失的。

新的设计:

deepseek_mermaid_20250921_d1b79a

deepseek_mermaid_20250921_8298c1

业务层(插件层)- 纯粹的同步逻辑

// 转账结果类
public class TransferResult {private final String transferOrderNo;private final boolean success;private final String message;public TransferResult(String transferOrderNo, boolean success, String message) {this.transferOrderNo = transferOrderNo;this.success = success;this.message = message;}// Getterspublic String getTransferOrderNo() { return transferOrderNo; }public boolean isSuccess() { return success; }public String getMessage() { return message; }@Overridepublic String toString() {return "TransferResult{" +"transferOrderNo='" + transferOrderNo + '\'' +", success=" + success +", message='" + message + '\'' +'}';}
}// 账户转账服务 - 纯粹的业务逻辑,完全同步
public class AccountTransferService {private final AccountingService accountingService = new AccountingService();// 同步执行转账,不包含任何异步或通知逻辑public TransferResult accountingSync(AccountTransfer accountTransfer) {System.out.println("转账记账开始 - 同步执行");try {// 同步处理转出账户AccountingRequest fromRequest = new AccountingRequest(accountTransfer.getTransferOrderNo(), accountTransfer.getFrom(), accountTransfer.getTransferAmount(), true);accountingService.accounting(fromRequest);// 同步处理收款方Map<String, Double> tos = accountTransfer.getTos();for (Map.Entry<String, Double> entry : tos.entrySet()) {AccountingRequest toRequest = new AccountingRequest(accountTransfer.getTransferOrderNo(),entry.getKey(),entry.getValue(),false);accountingService.accounting(toRequest);}return new TransferResult(accountTransfer.getTransferOrderNo(),true,"转账成功完成");} catch (Exception e) {return new TransferResult(accountTransfer.getTransferOrderNo(),false,"转账失败: " + e.getMessage());}}
}// 记账服务
public class AccountingService {public void accounting(AccountingRequest request) {System.out.println("AccountingService->记账完成. " +"业务单号=" + request.getOrderNo() +", accountNo=" + request.getAccountNo() +", amount=" + request.getAmount() +(request.isFrom() ? " (转出)" : " (转入)"));}
}// 账户转账请求
public class AccountTransfer {private String transferOrderNo;private String from;private double transferAmount;private Map<String, Double> tos;// Getters and setterspublic String getTransferOrderNo() { return transferOrderNo; }public AccountTransfer setTransferOrderNo(String transferOrderNo) { this.transferOrderNo = transferOrderNo; return this;}public String getFrom() { return from; }public AccountTransfer setFrom(String from) { this.from = from; return this;}public double getTransferAmount() { return transferAmount; }public AccountTransfer setTransferAmount(double transferAmount) { this.transferAmount = transferAmount; return this;}public Map<String, Double> getTos() { return tos; }public AccountTransfer setTos(Map<String, Double> tos) { this.tos = tos; return this;}
}// 记账请求
public class AccountingRequest {private final String orderNo;private final String accountNo;private final double amount;private final boolean isFrom;public AccountingRequest(String orderNo, String accountNo, double amount, boolean isFrom) {this.orderNo = orderNo;this.accountNo = accountNo;this.amount = amount;this.isFrom = isFrom;}// Getterspublic String getOrderNo() { return orderNo; }public String getAccountNo() { return accountNo; }public double getAmount() { return amount; }public boolean isFrom() { return isFrom; }
}
  1. 应用层 - 包含异步和通知逻辑
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;// 应用层服务 - 包含异步和通知逻辑
public class TransferOrderService {private final AccountTransferService accountTransferService;private final ExecutorService executorService;public TransferOrderService() {this.accountTransferService = new AccountTransferService();this.executorService = Executors.newFixedThreadPool(3);}// 应用层决定如何使用业务层服务(同步或异步)public void transfer() {System.out.println("转账单记账 - 应用层");// 准备转账数据AccountTransfer accountTransfer = new AccountTransfer();accountTransfer.setTransferOrderNo("T202508000001").setFrom("A").setTransferAmount(50.00);accountTransfer.setTos(Map.of("B", 10.00, "C", 40.00));// 应用层决定使用异步方式调用业务层服务executorService.submit(() -> {try {// 调用业务层的同步方法TransferResult result = accountTransferService.accountingSync(accountTransfer);// 应用层处理结果和通知if (result.isSuccess()) {onTransferSuccess(result);} else {onTransferFailure(result);}} catch (Exception e) {System.err.println("转账过程中发生异常: " + e.getMessage());onTransferFailure(new TransferResult(accountTransfer.getTransferOrderNo(),false,"转账异常: " + e.getMessage()));}});}// 应用层处理成功通知public void onTransferSuccess(TransferResult result) {System.out.println("=========当前是在应用层,进行转账完成后的业务处理");System.out.println("=========已向收款人发送到账通知短消息");System.out.println("=========转账结果: " + result);// 这里可以添加更多的应用层业务逻辑// 比如:更新订单状态、发送消息、记录日志等}// 应用层处理失败通知public void onTransferFailure(TransferResult result) {System.out.println("=========转账失败,执行补偿操作");System.out.println("=========失败原因: " + result.getMessage());// 这里可以添加失败处理逻辑// 比如:回滚操作、发送告警等}// 关闭线程池public void shutdown() {executorService.shutdown();}
}
  1. 演示主程序
public class ApplicationDemo {public static void main(String[] args) {// 创建应用层服务TransferOrderService transferOrderService = new TransferOrderService();// 执行转账transferOrderService.transfer();// 等待异步操作完成try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 清理资源transferOrderService.shutdown();}
}

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

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

相关文章

GEE训练教程:Sentinel-2卫星影像揭秘飓风奥蒂斯破坏力 - 指南

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

设置Redis在CentOS7上的自启动配置

在CentOS 7系统中,要设置Redis服务的自启动,需要配置Redis服务以便它能够在系统启动时自动运行。为此,我们将使用 systemctl命令,这是CentOS 7 中管理服务的推荐方法。 首先,确保已经正确地安装了Redis服务并且它…

挂载配置文件以Docker启动Redis服务

要使用Docker启动Redis服务,并挂载配置文件,首先需要确保已经安装好Docker环境。以下是具体步骤和相关的解释: 步骤1:准备Redis配置文件 您需要准备一个Redis配置文件,此文件会包含Redis服务器的配置指令。创建一…

abc418d

AtCoder ABC418 D XNOR Operation link 题意 给定一个长度为 \(n\) 的 01 串 \(s\),每次可以选择相邻的两个位置。如果两个位置字符相同,把它们缩成 \(1\),否则缩成 \(0\)。求 \(s\) 中有多少个子串经过操作可以变成…

Chapter 6 Joining Images

# 这个是numpy的功能 # imgHor = np.hstack((img, img)) # imgVer = np.vstack((img, img))def stackImages(scale, imgArray):rows = len(imgArray)cols = len(imgArray[0])rowsAvailable = isinstance(imgArray[0], …

动态主机配置协议(DHCP)中的中继机制及其配置

动态主机配置协议(Dynamic Host Configuration Protocol, DHCP)是一种网络协议,用于自动分配IP地址和其他网络配置信息给网络设备。在一个复杂的网络环境中,尤其是在不同子网之间,一台DHCP服务器可能无法直接为所…

DDD - 概念复习

领域 在 DDD 中,“领域(Domain)” 指的是软件要解决的 “业务范围” 及其包含的所有业务概念、规则和逻辑。 简单来说:如果你开发的是 “电商系统”,那么 “电商” 就是核心领域,包含 “商品、订单、支付、物流”…

进一步理解自适应卡尔曼滤波(AKF) - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

CSP-J1S1_2025

考点小记与错题整理。考点小记等比数列求和公式 已知等比数列 \(\{a_n\}\) ,公比为 \(q\),前 \(n\) 项和为 \(S_n\) 。 则有 \(S_n = \begin{cases} na_1, &q = 1 \\ \large \frac{a_1(1 - q ^ n)}{1 - q}, &…

完整教程:基于Spring Boot植物销售管理系统的设计与实现

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

​​Final Cut Pro 11.0 for Mac 剪视频安装教程|DMG文件安装步骤详解​(附安装包)

​​Final Cut Pro 11.0 for Mac 剪视频安装教程|DMG文件安装步骤详解​(附安装包)​一、下载文件 首先,你得先把这个 ​​Final Cut Pro 11.0 for Mac.dmg​​ 文件下载到你的 Mac 上。 安装包下载:https://pan.…

Vdd Vcc

Vdd Vcc二、在STM32中的具体含义和关系 对于STM32这类现代MCU,VCC和VDD的用法非常明确: 1. VDD / VDDA含义:I/O端口和外部外设的模拟电源。功能:这是给芯片的GPIO引脚驱动电路、部分外部外设以及模拟-to-数字转换器…

实用指南:物联网赋能24H共享书屋:智能化借阅管理的完整解决方案!

实用指南:物联网赋能24H共享书屋:智能化借阅管理的完整解决方案!2025-09-21 11:30 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !…

基于ThinkPHP实现动态ZIP压缩包的生成

在ThinkPHP框架中生成动态ZIP压缩包涉及到文件处理和压缩包管理,可以通过PHP的ZipArchive类来实现。下面逐步介绍如何在ThinkPHP框架中实现动态ZIP压缩包的生成首先,确保你的PHP环境支持zip扩展。 在你的控制器中添加…

使用Java实现用户的注册和登录流程

第一步:构建用户模型 首先,我们需要一个用户模型,以 Java 类的形式表现: public class User {private String username;private String password; // 注意:实际生产中密码应加密存储// 构造函数、getter 和 sette…

Windows安装Kafka(kafka_2.12-3.9.1),配置Kafka,以及遇到的困难解决方案

Windows安装Kafka(kafka_2.12-3.9.1),配置Kafka,以及遇到的困难解决方案pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family…

准备工作之动态内存分配[基于郝斌课程]

定义一块内存可以用数组定义,也可以动态分配: 使用数组定义一块内存,则该块内存是静态的,也就是一旦定义之后,这块内存的大小就固定了,例如,数组元素个数是5,则定义后,这=这块内存大小就是5,不能再改变 但是…

2025.6第一套六级听力生词

"Escalate" 是一个英语动词,在不同语境下有不同的含义,常见用法包括: 升级/加剧‌ 指问题、冲突或局势的严重性增加。例句:The dispute escalated into a full-scale war.(争端升级为全面战争。) (正…

CSP-S 2025游记

初赛 day -1 赛前最后一次去机房 , 尽管前三年都随便考过了 , 但今年不知道为什么特别紧张 , 害怕考以前没出过的类型 (伏笔) day 0 足球赛 被虐了 , 给腿跑软了 , 顶级后卫这一块 中午一点压力都没有了 , 睡觉是…

Chapter 5 Wrap Perspective

# width, height = 458, 371 width, height = 250, 350pts1 = np.float32([[109, 220], [282, 189], [154, 483], [353, 434]]) pst2 = np.float32([[0, 0], [width, 0], [0, height], [width, height]])matrix = cv2.…