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

模板方法模式:复杂业务代码的解耦与复用之道

在经典的DAO - Service (业务层) - Controller三层架构中模板方法模式Template Method Pattern的最佳落地位置通常是Service 层抽象基类。为什么放在 Service 层Controller 层太薄主要负责参数校验、协议转换和调用 Service不适合承载复杂的业务流程骨架。放在改层代码不方便复用在三层架构中controller原则上不允许写业务逻辑。DAO 层太底层只负责单表的 CRUD 或简单查询无法跨越多张表或外部系统编排复杂的“业务流”。Service 层是核心业务逻辑的核心流转如事务控制、前置校验、核心计算、后置通知、异常处理都在这里。不同子业务如不同类型的订单、不同渠道的支付往往遵循相同的流程但具体实现细节不同。设计思路基于 Service 层的模板方法架构1. 架构分层职责Controller接收请求转换为 DTO调用AbstractService.execute()。Service (Abstract)定义final的模板方法编排流程开启事务 - 校验 - 处理 - 记录 - 提交事务。Service (Concrete)继承抽象类实现具体的校验逻辑、核心处理逻辑。DAO被 Service 调用提供数据持久化能力模板方法中会调用不同的 DAO 组合。2. 核心设计图AbstractBusinessServicefinal execute(Context ctx)#abstract validate(Context ctx)#abstract doProcess(Context ctx)#abstract postProcess(Context ctx)-logAction(Context ctx)ConcreteServiceA-orderDao-inventoryDaovalidate()doProcess()postProcess()ConcreteServiceB-vipOrderDao-pointDaovalidate()doProcess()postProcess()OrderDAOInventoryDAO复杂业务案例多类型供应链采购入库系统业务背景某大型零售企业的供应链系统需要处理多种类型的采购入库单普通商品入库标准流程校验数量更新库存生成财务应付账款。生鲜商品入库需额外校验保质期、温度记录若不合格直接拒收合格则生成损耗预估单。跨境保税入库需先调用海关接口申报申报通过后才能入库并生成保税账册记录。共同流程算法骨架前置准备加载单据详情锁定数据库记录防止并发。业务校验根据商品类型执行不同的校验规则。核心入库更新库存表生成入库流水。衍生处理生成财务单据、通知上下游、发送消息。后置清理释放锁记录操作日志。差异点校验规则不同、核心入库时的附加字段不同、衍生处理的逻辑完全不同。代码实现1. 定义上下文对象 (Context)用于在模板方法的各个步骤间传递数据避免参数列表过长。DatapublicclassInboundContext{privateLongorderId;privateStringorderType;// NORMAL, FRESH, CROSS_BORDERprivateInboundOrderorderInfo;privateListOrderItemitems;privatebooleanisSuccess;privateStringfailReason;// 用于步骤间传递临时数据privateMapString,ObjectextraDatanewHashMap();}2. 抽象 Service 层 (模板核心)这里使用 Spring 的Transactional保证整个流程的事务性。ServicepublicabstractclassAbstractInboundService{AutowiredprivateInboundOrderDaoinboundOrderDao;AutowiredprivateOperationLogDaologDao;AutowiredprivateMessageProducermessageProducer;/** * 模板方法定义不可变的业务流程骨架 * 使用 final 防止子类修改流程顺序 */Transactional(rollbackForException.class)publicfinalvoidexecute(InboundContextcontext){try{// 1. 前置准备 (通用)prepare(context);// 2. 业务校验 (子类实现)validate(context);// 3. 核心入库处理 (子类实现)doInboundProcess(context);// 4. 衍生业务处理 (子类实现可选钩子)postInboundProcess(context);// 5. 通用后置发送成功消息sendSuccessNotification(context);context.setSuccess(true);}catch(BusinessExceptione){// 捕获业务异常标记失败context.setSuccess(false);context.setFailReason(e.getMessage());handleBusinessError(context,e);throwe;// 回滚事务}catch(Exceptione){// 捕获系统异常context.setSuccess(false);context.setFailReason(系统异常e.getMessage());handleSystemError(context,e);thrownewSystemException(入库流程执行失败,e);}finally{// 6. 最终清理记录日志 (无论成功失败都执行)logExecution(context);releaseLock(context);}}// --- 通用步骤实现 ---protectedvoidprepare(InboundContextcontext){// 加载订单详情context.setOrderInfo(inboundOrderDao.selectById(context.getOrderId()));context.setItems(inboundOrderDao.selectItems(context.getOrderId()));// 分布式锁逻辑 (伪代码)// lockService.lock(INBOUND_ context.getOrderId());System.out.println([通用] 加载订单并加锁: context.getOrderId());}protectedvoidsendSuccessNotification(InboundContextcontext){messageProducer.send(inbound.success,context.getOrderId());}protectedvoidhandleBusinessError(InboundContextcontext,BusinessExceptione){System.out.println([通用] 记录业务错误告警);}protectedvoidhandleSystemError(InboundContextcontext,Exceptione){System.out.println([通用] 记录系统异常堆栈并通知运维);}protectedvoidlogExecution(InboundContextcontext){OperationLoglognewOperationLog();log.setOrderId(context.getOrderId());log.setStatus(context.isSuccess()?SUCCESS:FAIL);log.setRemark(context.getFailReason());logDao.insert(log);}protectedvoidreleaseLock(InboundContextcontext){// lockService.unlock(INBOUND_ context.getOrderId());System.out.println([通用] 释放锁);}// --- 抽象步骤 (强制子类实现) ---/** * 步骤2校验逻辑 * 不同商品类型校验规则完全不同 */protectedabstractvoidvalidate(InboundContextcontext)throwsBusinessException;/** * 步骤3核心入库 * 更新库存表写入入库明细 */protectedabstractvoiddoInboundProcess(InboundContextcontext)throwsBusinessException;/** * 步骤4衍生处理 (钩子方法) * 默认空实现子类按需覆盖 */protectedvoidpostInboundProcess(InboundContextcontext)throwsBusinessException{// 默认不做任何事}}3. 具体业务实现类场景 A生鲜入库 (需校验保质期生成损耗单)ServicepublicclassFreshInboundServiceextendsAbstractInboundService{AutowiredprivateFreshStockDaofreshStockDao;AutowiredprivateLossEstimateDaolossEstimateDao;Overrideprotectedvoidvalidate(InboundContextcontext){System.out.println([生鲜] 校验保质期和温度记录...);for(OrderItemitem:context.getItems()){if(item.getExpireDays()3){thrownewBusinessException(生鲜商品剩余保质期不足3天拒收);}if(item.getTransportTemp()5){thrownewBusinessException(运输温度超标拒收);}}}OverrideprotectedvoiddoInboundProcess(InboundContextcontext){System.out.println([生鲜] 更新生鲜专用库存表记录批次号和生产日期...);// 调用 DAO 更新特定字段freshStockDao.batchInsert(context.getItems());}OverrideprotectedvoidpostInboundProcess(InboundContextcontext){System.out.println([生鲜] 计算预计损耗率生成损耗预估单...);// 特有逻辑生鲜需要预估损耗LossEstimateestimatecalculateLoss(context.getItems());lossEstimateDao.insert(estimate);context.getExtraData().put(lossId,estimate.getId());}privateLossEstimatecalculateLoss(ListOrderItemitems){// 复杂计算逻辑returnnewLossEstimate();}}场景 B跨境保税入库 (需先报关)ServicepublicclassCrossBorderInboundServiceextendsAbstractInboundService{AutowiredprivateBondedStockDaobondedStockDao;AutowiredprivateCustomsClientcustomsClient;// 调用外部海关接口AutowiredprivateBondedLedgerDaoledgerDao;Overrideprotectedvoidvalidate(InboundContextcontext){System.out.println([跨境] 校验备案清单状态和额度...);// 校验是否在海关备案清单内if(!customsClient.checkManifestStatus(context.getOrderId())){thrownewBusinessException(海关备案清单状态异常);}}OverrideprotectedvoiddoInboundProcess(InboundContextcontext){System.out.println([跨境] 调用海关接口申报入库...);// 关键差异必须先调外部接口成功后才写库StringcustomsNocustomsClient.declareInbound(context.getOrderInfo());context.getExtraData().put(customsNo,customsNo);System.out.println([跨境] 更新保税仓库存表...);bondedStockDao.batchInsert(context.getItems(),customsNo);}OverrideprotectedvoidpostInboundProcess(InboundContextcontext){System.out.println([跨境] 生成保税电子账册记录...);StringcustomsNo(String)context.getExtraData().get(customsNo);ledgerDao.createLedgerRecord(context.getOrderId(),customsNo);}// 甚至可以重写错误处理跨境失败可能需要触发自动重试报关OverrideprotectedvoidhandleBusinessError(InboundContextcontext,BusinessExceptione){super.handleBusinessError(context,e);if(e.getMessage().contains(海关)){System.out.println([跨境] 触发海关申报重试队列...);// retryQueue.add(context.getOrderId());}}}4. Controller 层调用Controller 层非常干净只需要根据类型获取对应的 Service 实例通常通过工厂模式或 Map 注入。RestControllerRequestMapping(/inbound)publicclassInboundController{// 通过 Map 注入所有实现类Key 为 Bean 名称或自定义注解值AutowiredprivateMapString,AbstractInboundServiceinboundServiceMap;PostMapping(/execute)publicResultVoidexecute(RequestBodyInboundDTOdto){InboundContextcontextconvertToContext(dto);// 根据类型路由到具体的 ServiceAbstractInboundServiceserviceinboundServiceMap.get(getServiceBeanName(context.getOrderType()));if(servicenull){returnResult.fail(不支持的入库类型);}// 执行模板方法service.execute(context);returnResult.success();}privateStringgetServiceBeanName(Stringtype){// 简单的映射逻辑实际可用策略模式优化查找switch(type){caseFRESH:returnfreshInboundService;caseCROSS_BORDER:returncrossBorderInboundService;default:returnnormalInboundService;// 假设有一个默认实现}}}这种设计的优势事务一致性保障在抽象类的execute方法上标注Transactional确保了从校验、入库到衍生处理的全过程要么全成功要么全回滚。子类无需关心事务边界避免了在子类中错误地拆分事务。流程标准化与合规对于金融、供应链等强合规场景prepare加锁、logExecution审计日志、releaseLock资源释放等关键步骤由父类强制执行子类无法跳过杜绝了“忘记写日志”或“忘记释放锁”的隐患。高内聚低耦合DAO 层保持纯粹的数据访问不掺杂业务判断。Service 层抽象类管流程具体类管业务规则。新增一种入库类型如“退货入库”只需新增一个类继承抽象类无需修改现有代码开闭原则。便于单元测试可以单独测试抽象类中的通用逻辑如日志记录是否正确。可以单独 Mock DAO 测试某个具体子类的业务逻辑如生鲜的保质期校验。应对复杂变化如果未来公司要求所有入库流程在“核心处理”前增加一步“AI 风险预测”只需在抽象类的execute方法中插入一行代码aiRiskCheck(context)所有子类自动生效无需逐个修改。总结在 DAO-Service-Controller 架构中将模板方法模式应用于 Service 层的抽象基类是处理复杂多变业务流的最佳实践。它既利用了面向对象的多态性来隔离差异又通过继承机制固化了核心流程非常适合中国企业中常见的“大流程统一、小细节各异”的业务场景如多银行支付、多物流对接、多省份政务对接等。

相关文章:

模板方法模式:复杂业务代码的解耦与复用之道

在经典的 DAO - Service (业务层) - Controller 三层架构中,模板方法模式(Template Method Pattern) 的最佳落地位置通常是 Service 层(抽象基类)。 为什么放在 Service 层? Controller 层太薄:…...

RAGFlow安装部署使用

RAGFlow安装部署使用教程 前言 在大模型应用越来越普及的今天,很多人都想要搭建属于自己的私有知识库,把公司的文档、个人的资料都变成可以对话的智能助手,但是又担心数据泄露,或是被复杂的部署流程劝退。 如果你也有这样的困扰…...

Python 数据可视化(二):多曲线对比、局部放大框(附源码)

在上一篇博客中,我们成功配置了所向披靡的 VS Code Conda 数据可视化环境。环境有了,画笔就位了,今天我们就来动真格的——手把手写代码,把数据变成能放进报告或论文的高清图!步骤 0:画图前的准备——什么…...

eDiary使用教程

eDiary使用教程CSDN文章 前言 在信息爆炸的今天,我们每天都有太多的思绪、工作笔记、生活点滴需要记录,却又担心隐私泄露,或是被臃肿的笔记软件拖慢效率。如果你也在寻找一款轻量、安全、无广告的本地记录工具,那么eDiary 电子日…...

GitHub霸榜!OpenHands开源炸裂:全能AI程序员真的来了?

阅读指引:这是一篇旨在打破“AI只会写Hello World”刻板印象的深度硬核测评。本文不仅是对OpenHands这一现象级开源项目的拆解,更是对未来软件工程形态的一次前瞻性推演。全文约 3500 字,阅读需 8 分钟,建议收藏后细读。00. 序章&…...

六大AI论文网站助力学术写作,提供智能降重与自然改写功能,减少重复率

开头总结工具对比(技能4) �� 为帮助学生们快速选出最适合的AI论文工具,我从处理速度、降重效果和核心优势三个维度,对比了6款热门网站,数据基于实际使用案例: 工具名称 处理速度 降…...

计算机毕业设计springboot停车场管理系统 基于SpringBoot框架的智能车库运营平台设计与实现 智慧停车服务系统——采用SpringBoot技术的车辆停放信息化解决方案

计算机毕业设计springboot停车场管理系统4z3jk9 (配套有源码 程序 mysql数据库 论文) 本套源码可以在文本联xi,先看具体系统功能演示视频领取,可分享源码参考。 二十一世纪以来,随着城市化进程加速和机动车保有量持续增长&#x…...

SM3 vs SHA-256:国密哈希算法与主流算法的性能对比测试(附Benchmark数据)

SM3与SHA-256深度性能评测:如何选择适合业务的哈希算法? 在数据安全领域,哈希算法如同数字世界的指纹采集器,将任意长度的数据映射为固定长度的"指纹"。当国密标准SM3遇上国际主流SHA-256,开发者该如何选择&…...

计算机毕业设计springboot基于web的英语学习网站 基于SpringBoot的在线英语教育平台设计与实现 基于B/S架构的智能英语学习系统开发

计算机毕业设计springboot基于web的英语学习网站 (配套有源码 程序 mysql数据库 论文) 本套源码可以在文本联xi,先看具体系统功能演示视频领取,可分享源码参考。随着全球化进程加速和国际交流日益频繁,英语作为国际通用语言的重要…...

仿生软体机器人实战:从蝠鲼游泳到管道爬行,5个惊艳案例解析

仿生软体机器人实战:从蝠鲼游泳到管道爬行,5个惊艳案例解析 当工程师们开始向自然界寻找灵感时,机器人技术便进入了一个全新的维度。想象一下,一条能在珊瑚礁间灵活穿梭的机械蝠鲼,或是在复杂管道系统中自如爬行的软体…...

孪生神经网络在变化检测中的应用:从CSCDNet到SSCDNet的演进与优化

孪生神经网络在语义场景变化检测中的技术演进与实践 当城市街景随时间流转,建筑物翻新或道路扩建时,如何让计算机像人类一样敏锐地捕捉这些变化?孪生神经网络正成为解决这一挑战的核心技术。不同于传统像素对比方法容易受光照、视角干扰&…...

MCP协议在VS Code中的高阶应用(2024企业级开发必掌握的4种动态上下文集成模式)

第一章:MCP协议核心机制与VS Code扩展生态全景图MCP(Model Communication Protocol)是一种面向大模型智能体协同的轻量级通信协议,其设计目标是在异构开发环境间建立标准化、可插拔的模型调用与状态同步通道。协议采用基于 JSON-R…...

为什么有的降AI工具降完还是高?深度分析工具选择的关键指标

为什么有的降AI工具降完还是高?深度分析工具选择的关键指标 花了钱、用了工具、等了半天,结果知网一查AI率还是45%。这种事我身边不止一个人遇到过。降AI工具效果差的原因可能有很多,但最关键的问题往往出在工具选择上。选对了工具&#xff…...

计算机毕业设计springboot遇见宠物生活馆系统设计与实现 基于SpringBoot的萌宠驿站综合服务管理平台设计与实现 SpringBoot框架下爱宠家园一站式服务平台的设计与实现

计算机毕业设计springboot遇见宠物生活馆系统设计与实现n6ea5118 (配套有源码 程序 mysql数据库 论文) 本套源码可以在文本联xi,先看具体系统功能演示视频领取,可分享源码参考。随着社会经济的持续发展和居民生活水平的不断提升,饲…...

智慧仓储空间智能管理系统技术方案:基于三维重构与轨迹建模的全流程透明化与智能决策体系

《智慧仓储空间智能管理系统技术方案》副标题:基于三维重构与轨迹建模的全流程透明化与智能决策体系发布单位:镜像视界(浙江)科技有限公司一、项目背景:仓储管理正在从“经验驱动”走向“空间智能驱动”随着仓储规模的…...

重塑社区体验:打造无广告干扰的第三方酷安客户端

重塑社区体验:打造无广告干扰的第三方酷安客户端 【免费下载链接】c001apk fake coolapk 项目地址: https://gitcode.com/gh_mirrors/c0/c001apk c001apk作为一款基于官方客户端二次开发的第三方应用,采用Jetpack Compose框架与MVI架构模式&#…...

【2026 最新】一篇文章告诉你什么是Skills 同时 告别Prompt工程!用Claude Skills把AI变成你的专属打工人

在人工智能领域,尤其是在 AI 智能体(AI Agent)的语境下,Skills (技能)是一个核心概念。简单来说,它是让 AI 从“会思考”的聊天机器人,进化为“会做事”的数字助理的关键。 你可以把它理解为 A…...

2026.3.20 用EasyExcel实现excel报表的导入与导出

2026.3.20 用EasyExcel实现excel报表的导入与导出1.在自己模块创建一个实体类Datapublic class User {/*** value表示该属性对应的表头名称, index表示该属性所处的列的位置*///该注解能建立Java对象与表格列之间的映射关系ExcelProperty(value "编号", …...

BERT模型实战:input_ids和attention_mask参数详解与避坑指南

BERT模型实战:input_ids和attention_mask参数详解与避坑指南 在自然语言处理领域,BERT模型已经成为处理文本任务的基石。对于刚接触BERT的开发者来说,理解其输入参数的运作机制是成功应用的第一步。本文将深入剖析input_ids和attention_mask这…...

AIGC检测算法更新后AI率飙升?完整应对攻略来了

AIGC检测算法更新后AI率飙升?完整应对攻略来了 最近两周,身边不少同学都遇到了同一个问题——之前查过一遍AI率只有12%左右,过了几天再查,直接蹦到了45%甚至更高。一开始以为是自己操作有问题,后来一打听才知道&#x…...

从LeNet到EfficientNet:手把手带你复现CNN进化史上的几个关键‘拐点’模型

从LeNet到EfficientNet:代码实战CNN架构演进的关键突破 在计算机视觉领域,卷积神经网络(CNN)的进化史堪称一部微缩的深度学习发展史。每当一个新的架构出现,往往伴随着性能的显著提升或计算效率的突破。对于真正希望理…...

tcpdump 抓包工具实战技巧与高级过滤指南(下)

1. 逻辑运算符的高级组合技巧 在真实网络环境中,我们经常需要同时满足多个条件才能精准捕获目标数据包。tcpdump支持三种基本逻辑运算符:and(与)、or(或)、not(非)。这些运算符可以组…...

为什么越来越多的程序员都转岗网络安全,网络安全好在哪里?

相信百分之99%的人都不知道程序员为什么要转行学网络安全,将程序员与渗透人员进行对比,你就清楚了。 业内都知道程序员的35岁下岗门槛,日常加班严重,996是常事,竞争压力大,一个岗位几十人投递,…...

保姆级教程:用PNNX将PyTorch模型一键转成NCNN(附动态输入配置)

深度学习模型高效部署指南:PyTorch到NCNN的无缝转换实战 在移动端和边缘计算设备上部署深度学习模型时,开发者常面临框架兼容性和性能优化的双重挑战。本文将详细介绍如何通过PNNX工具链,将训练好的PyTorch模型高效转换为NCNN格式&#xff0c…...

Allpairs+Deepseek组合测试实战:5分钟搞定正交表用例生成(附常见报错解决方案)

AllpairsDeepseek组合测试实战:5分钟搞定正交表用例生成(附常见报错解决方案) 在软件测试领域,组合测试一直是提高测试效率的关键技术。传统测试方法在面对多因素组合场景时,往往会产生用例数量爆炸的问题,…...

S7-200Smart恒压供水与485通讯及触摸屏程序样例合集:案例解析与参数设置

S7-200Smart 恒压供水程序样例485通讯样例 触 摸屏样例子。 1.此程序样例为一拖二恒压供水样例,采用S7-200Smart PLC和smart 700触摸屏人机与abb变频器485通讯执行变频器PID实现恒压供水,商品同样包含S7-200PLC程序 2.程序为实际操作项目案例程序&…...

三菱PLC与变频器Modbus通讯实战:从原理到应用

三菱FX1N PLC 485与三菱变频器modbus通讯可直接拿来实用了,三菱FX PLC与三菱变频器通讯 采用器件:三菱FX1N PLC,FX1N485BD板,1台三菱E740变频器,三菱FX2N FX2N 485BD板同样适用,中间触摸屏采用昆仑通态MCGS…...

电池材料行业数据管理新突破:AI4S驱动的科学数据平台正在重塑电池材料开发范式

电池行业背景描述:电池材料行业是新能源汽车、储能设备等新能源领域的基础产业,近年来随着全球能源转型和电动汽车等新能源应用的快速发展,电池材料行业得到了快速发展。在我国,锂电池作为主要电池类型,其产业链不断完…...

PDF表格数据处理避坑指南:为什么你的pdfplumber提取不到数据?

PDF表格数据提取实战:避开pdfplumber的5大隐形陷阱 第一次用pdfplumber提取PDF表格时,我盯着屏幕上那堆错位的文字和缺失的边框,差点以为下载了假的Python库。直到后来才发现,问题从来不在工具本身,而在于PDF这种"…...

InceptionV3网络设计精要:从1x1卷积到多尺度融合的工程智慧

InceptionV3架构解密:1x1卷积与多尺度特征融合的工程艺术 在计算机视觉领域,卷积神经网络(CNN)的设计一直面临着两个核心挑战:如何高效捕捉多尺度特征,以及如何在计算资源有限的情况下最大化模型性能。Google团队提出的InceptionV…...