news 2026/10/20 9:01:45

模板方法模式实战指南:骨架复用、钩子方法与工程设计

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
模板方法模式实战指南:骨架复用、钩子方法与工程设计

模板方法模式是我在实际开发里用得最顺手的一类行为型设计模式之一,它解决的核心痛点是:一个业务流程骨架稳定,但其中几个步骤在不同场景下表现不同。与其把重复的流程代码在各处复制粘贴,不如把流程骨架锁定在父类里,把容易变的部分留给子类去填空。这篇文章就围绕这个模式展开,从概念、代码、实战到踩坑,完整走一遍,适合所有想真正把模板方法用到工程里的开发者。

1. 模板方法模式:先把定义翻译成人话

1.1 一套固定流程,中间几个步骤我来定

官方定义通常会这样说:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变算法的结构即可重新定义算法的某些特定步骤。这个定义听起来略显抽象,我习惯用一个生活场景去理解它。

你在驾校学车时,不管报名的是C1手动挡还是C2自动挡,整个学车流程都是固定的:报名缴费、预约练车、上车练习、预约考试、实际考试、领取驾照。这个流程的骨架不能乱动,总不能跳过去考试先拿驾照。但每个环节的具体内容,手动挡和自动挡差异巨大,练车内容不一样,考试车型不一样,连油门刹车配合的逻辑都不一样。驾校把流程骨架定死了,学员、教练、考试中心在不同的环节填入不同内容,这就是模板方法模式最简单直观的映射。

放到程序世界,模板方法模式要解决的是典型的"重复但又不完全一样"的问题。我举一个电商系统里常见的例子:创建订单的时候,普通订单、秒杀订单、团购订单都要经历校验库存、锁定库存、生成订单、扣减库存、记录日志这个流程,但校验规则不同、库存锁定方式不同、后续的库存处理策略也不同。如果没有模板方法,大家多半会写三个方法把同样一段骨架代码复制三遍,后面流程一旦增加一个步骤,比如新增优惠券核销,三个地方就得同步改三次,改漏一个就是故障。

1.2 好莱坞原则:别打电话给我们,我们会打给你

模板方法模式背后的设计思想,在行业内有个很有意思的代号叫"好莱坞原则":Don't call us, we'll call you. 意思是,演员不要主动给导演打电话问自己有没有角色,导演看中了自然会联系你。映射到代码里,就是父类定义好流程骨架,在合适的时机主动调用子类实现的方法,子类永远不需要在某个业务逻辑里主动去调用父类的模板方法。

为什么这个原则很重要?因为它是一种典型的控制反转。普通的继承关系里,子类依赖父类提供的能力,所以子类主动调用父类是常见操作;但模板方法模式把这个关系倒过来了,父类在流程里掌握主动,它来决定"你这些步骤什么时候执行、以什么顺序执行、哪些步骤必须执行、哪些步骤可以跳过"。子类只负责把父类需要的能力准备好,至于这个能力在流程里什么时候会被用上、会被用几次,子类完全不关心。

这个思路其实和你要写一个框架的核心思路是一模一样的。框架做的事情就是定义一个固定的处理流程,然后在某些特定节点留出口子让使用者插入自定义逻辑。比如一个Web框架处理HTTP请求的骨架,从接收请求、路由解析、参数绑定、执行业务逻辑、渲染响应到记录日志,这个骨架被框架写死,但你可以在特定的中间件位置插入自己的逻辑。你不需要知道请求何时到达、框架何时回调,你只负责把逻辑填进该填的位置。

这也解释了为什么模板方法模式在框架设计、工作流引擎、代码生成器这类场景里出镜率极高。只要"流程稳定、节点可变"这个条件成立,模板方法基本上就是最优解。

2. 代码落地:从朴素实现到模板方法模式

2.1 最初代码:复制粘贴的三遍

先看一个没有任何设计模式加持的朴素版本。假设我现在要做一个数据导出功能,分别支持CSV、Excel、PDF三种格式。第一版代码通常是这样的:

public class CsvExporter { public void export() { // 1. 查询数据 List<Map<String, Object>> data = queryData(); // 2. 加工数据,统一格式 List<String[]> processed = processData(data); // 3. 渲染CSV文件 String csvContent = renderCsv(processed); // 4. 保存文件 saveFile(csvContent, "export.csv"); // 5. 记录日志 log("CSV导出完成"); } } public class ExcelExporter { public void export() { List<Map<String, Object>> data = queryData(); List<String[]> processed = processData(data); Workbook workbook = renderExcel(processed); saveWorkbook(workbook, "export.xlsx"); log("Excel导出完成"); } } public class PdfExporter { public void export() { List<Map<String, Object>> data = queryData(); List<String[]> processed = processData(data); byte[] pdfBytes = renderPdf(processed); saveBytes(pdfBytes, "export.pdf"); log("PDF导出完成"); } }

这段代码最大的问题不是代码重复,虽然看起来第二步加工数据逻辑可能重复了三份。真正的问题是业务流程的稳定性没有被显式声明。今天三个导出器都遵守这个流程,明天如果业务方要求"所有导出在保存之前必须校验文件大小,超过阈值要压缩",你就得去三个类里各加一段校验逻辑。三个类还算能接受,如果是三十个导出器呢?你只能靠搜索文件名然后把代码逐段补进去,补漏一次就是线上事故。

2.2 上模板方法:骨架收敛,子类填空

用模板方法模式重构之后,我们把完整的流程骨架搬到抽象的父类里,子类只实现真正有差异的步骤。依然以导出功能为例,重构后的结构是这样的:

public abstract class AbstractExporter { public final void export() { List<Map<String, Object>> data = queryData(); List<String[]> processed = processData(data); renderAndSave(processed); log("导出完成"); } protected abstract List<Map<String, Object>> queryData(); protected abstract List<String[]> processData(List<Map<String, Object>> data); protected abstract void renderAndSave(List<String[]> processed); private void log(String message) { System.out.println(now() + " " + message); } } public class CsvExporter extends AbstractExporter { @Override protected List<Map<String, Object>> queryData() { // CSV的数据来源是数据库查询,走分页逻辑 } @Override protected List<String[]> processData(List<Map<String, Object>> data) { // 把Map格式统一转换成字符串数组 } @Override protected void renderAndSave(List<String[]> processed) { // 拼CSV字符串并写文件 } }

注意我把export方法加上了final修饰。这是模板方法模式一个非常关键的习惯:模板方法本身不允许子类覆写。如果你不锁死,某个子类心情不好直接 override 了整个 export 方法,流程骨架就形同虚设,模式直接失效。我见过不少初写这个模式的同学会把这个 final 省略,后续维护的人看到 export 是个普通方法,顺手就覆写了,整个一致性就被破坏了。所以,模板方法用 final 锁死,别犹豫。

在这个结构里,父类是全流程的唯一入口,子类只负责回答三个问题:数据从哪来、数据长什么样、数据以什么格式落盘。将来要新增 docx 导出,只需要新写一个子类实现三个填空方法,不需要动任何已有代码。这就是模板方法模式最核心的收益:算法骨架复用、变更局部化、扩展只增不改。

2.3 钩子方法:让子类也能干预流程

上面的例子把"可变的步骤"抽成了抽象方法,要求所有子类必须实现。但在实际业务里,还有一类变点更温柔:有的子类需要,有的子类不需要,甚至同一个子类在不同配置下表现还不一样。这时候如果全部用抽象方法,会让子类被迫实现一堆压根用不上的逻辑。

这种情况就要引入钩子方法。钩子方法是一种已经被父类提供默认实现的方法,默认实现要么为空、要么是一个通行的行为,子类可以选择覆写它,从而改变流程的某个局部表现,子类也可以选择不覆写,直接沿用默认行为。

我把导出功能再升级一下,现在增加一个需求:有的导出场景需要在文件内容最前面生成表头行,有的场景不需要。同时,导出完成之后,有的场景需要给管理员发一个通知,有的场景不需要。这个需求如果用抽象方法实现,每个子类都要写一个"是否要表头"的判断逻辑,很啰嗦。用钩子方法则优雅很多:

public abstract class AbstractExporter { public final void export() { // 1. 查询数据 List<Map<String, Object>> data = queryData(); // 2. 加工数据 List<String[]> processed = processData(data); // 3. 是否需要表头,钩子方法决定 if (needHeader()) { addHeader(processed); } // 4. 渲染并保存 renderAndSave(processed); // 5. 是否需要通知,钩子方法决定 if (needNotify()) { sendNotification(); } // 6. 记录日志 log("导出完成"); } protected abstract List<Map<String, Object>> queryData(); protected abstract List<String[]> processData(List<Map<String, Object>> data); protected abstract void renderAndSave(List<String[]> processed); protected boolean needHeader() { return true; // 默认需要表头 } protected boolean needNotify() { return false; // 默认不发通知 } private void addHeader(List<String[]> processed) { // 在数据最前面插入表头行 } private void sendNotification() { // 发送站内信或邮件 } } public class CsvExporter extends AbstractExporter { @Override protected List<Map<String, Object>> queryData() { ... } @Override protected List<String[]> processData(List<Map<String, Object>> data) { ... } @Override protected void renderAndSave(List<String[]> processed) { ... } @Override protected boolean needNotify() { return true; // CSV导出的数据量大,导出完要通知管理员 } }

钩子方法的精髓在于"默认行为"的存在。子类面对钩子时,只有两个选择:要么继承默认行为,要么覆写它。这个模式给了子类一种"可参与但不强制"的干预能力,也让模板方法在保持骨架稳定的前提下拥有了一定的柔性。

在设计钩子方法时有一个原则值得特别注意:钩子方法的可见性要设置为 protected,因为它本来就不是给外部调用的,它是给子类覆写用的。如果你把钩子设成 public,外部代码会看到一个空方法或者一个含义模糊的开关方法,容易引起误用。我在实际代码审查里经常提醒团队,凡是模板方法模式里的钩子,一律 protected,不要暴露到类的外部接口里。

3. 完整实战:报表导出中心的模板方法设计

3.1 需求拆解与流程制定

光靠泡茶泡咖啡、导出文件这些例子还不足以体现模板方法模式在复杂业务里的威力。我拿一个相对完整的实际项目来说明,这是我之前在一套后台管理系统里做过的报表导出中心,它的需求如下:

平台每天会生成多种业务报表,运营同学可以把任意报表导出成 CSV 或 Excel,后续还可能增加 PDF 格式。所有导出任务都要做权限校验、数据量限制、文件上传到对象存储、写入导出记录、发送站内信通知。不同报表的数据来源不同,有的是查订单库、有的是统计用户行为、有的是聚合多个接口的数据;数据加工规则也不一样,有的是直接透传,有的是做脱敏,有的是把枚举值转成中文。

如果按照最原始的写法,每个报表类型和每种导出格式组合一套代码,假设有10类报表,支持2种格式,那就是20个相似度超过70%的类。这显然是不可接受的。用模板方法模式后,我把整个导出的业务流程在抽象父类里固定下来,每个报表类型只实现数据获取和加工两个核心逻辑,导出格式的渲染差异单独抽象一层。

设计后的流程是这个样子:

1. 校验导出权限(可跳过,钩子) 2. 组装查询参数(强制实现,原语操作) 3. 执行数据查询(强制实现,原语操作) 4. 对数据做加工处理(强制实现,原语操作) 5. 检查数据量是否超限(父类统一实现) 6. 渲染指定格式的文件(委托给格式渲染器) 7. 上传文件到对象存储(父类统一实现) 8. 写入导出日志(父类统一实现) 9. 发送通知(可跳过,钩子)

3.2 核心代码解析

整个抽象父类的核心代码是这样组织的:

public abstract class AbstractReportExporter { public final void export(ExportRequest request) { // 1. 权限校验,钩子方法,默认跳过 if (!checkPermission(request)) { throw new ForbiddenException("无导出权限"); } // 2. 组装查询参数 QueryParam param = buildQueryParam(request); // 3. 查询数据 List<Map<String, Object>> rawData = queryData(param); // 4. 数据加工 List<Map<String, Object>> processedData = processData(rawData, param); // 5. 数据量限制检查,父类统一逻辑 if (processedData.size() > getMaxExportRows()) { throw new ExportLimitExceededException("超出最大导出行数"); } // 6. 渲染文件,委托给格式渲染器 ExportFile exportFile = renderer.render(processedData, request.getFormat()); // 7. 上传对象存储 String fileUrl = uploadFile(exportFile); // 8. 写日志 writeExportLog(request, fileUrl); // 9. 发送通知,钩子方法,默认不发 if (needNotify()) { sendNotify(request); } } protected abstract QueryParam buildQueryParam(ExportRequest request); protected abstract List<Map<String, Object>> queryData(QueryParam param); protected abstract List<Map<String, Object>> processData( List<Map<String, Object>> rawData, QueryParam param); protected boolean checkPermission(ExportRequest request) { return true; } protected boolean needNotify() { return false; } protected int getMaxExportRows() { return 50000; } protected final void writeExportLog(ExportRequest request, String fileUrl) { // 统一的日志写入逻辑 } protected final String uploadFile(ExportFile exportFile) { // 统一的对象存储上传逻辑 } }

有一个细节我想特别说一下,就是uploadFile和writeExportLog这两个方法我用 final 修饰了。在模板方法模式里,并不是所有步骤都应该下沉给子类实现。像文件上传、日志记录这类流程,如果允许子类各自覆写,很容易出现不同的报表导出走了不同的文件上传通道、日志格式不统一的情况,给后续排查问题增加成本。我的习惯是:步骤阶段性的细节尽量收拢到父类,把可变面缩小到真正需要差异化的几个原语操作上。

另一个细节是getMaxExportRows。我做成了钩子,默认50000行,个别超大报表可以覆写调高。这个考虑是,业务上确实存在特殊报表需要导出更多数据,但又不希望所有报表都放开限制,钩子方法在这个场景里非常适合。

3.3 扩展点设计:加一个新的导出格式要动多少代码

这个项目上线后遇到一个新需求:运营要增加 PDF 导出格式。如果用多继承加条件分支的实现方式来写,这个需求可能要动十几个类。但模板方法模式下,这个扩展是一个标准的"开闭原则"演示。

我把渲染环节抽成了一个ReportRenderer接口,它本身不依赖模板方法模式,但它让模板方法得到更好的扩展性:

public interface ReportRenderer { ExportFile render(List<Map<String, Object>> data, ExportFormat format); } public class CsvRenderer implements ReportRenderer { @Override public ExportFile render(List<Map<String, Object>> data, ExportFormat format) { // CSV 渲染逻辑 return new ExportFile("csv", contentBytes); } } public class ExcelRenderer implements ReportRenderer { @Override public ExportFile render(List<Map<String, Object>> data, ExportFormat format) { // Excel 渲染逻辑 return new ExportFile("xlsx", bytes); } }

抽象父类通过构造器注入ReportRenderer:

public abstract class AbstractReportExporter { private final ReportRenderer renderer; protected AbstractReportExporter(ReportRenderer renderer) { this.renderer = renderer; } // export 方法里的渲染步骤就调用 renderer.render(data, format) }

将来要支持 PDF,只需要新增PdfRenderer,再写一个具体的子类继承AbstractReportExporter,完成查询参数组装、数据查询和数据加工这三个原语操作。原有代码完全不需要改动,这个扩展成本已经低到极致了。

我在一开始设计时对"格式渲染"到底放在模板方法里做还是单独抽接口,犹豫过一段时间。最终选择抽接口的理由是:同一份数据,今天的导出格式是 CSV、Excel 二选一,明天可能出现一个报表同时生成多种格式的需求。如果渲染逻辑写死在具体子类里,到时候一个子类要依赖多个导出器的代码,就会很别扭。单独抽一个渲染器接口,可以让模板方法和渲染策略各自演进,这个拆分我后来验证是值得的。

4. 模板方法 vs 策略模式:别再纠缠不清

4.1 一个"全替换",一个"局部替换"

几乎所有学习设计模式的人都会在模板方法和策略模式之间犯一次迷糊,因为这两个模式都是在解决"算法变化"的问题。但它们的本质差异非常清晰:策略模式是用组合的方式,把整个算法封装成独立的对象,运行时可以整体替换;模板方法模式是用继承的方式,把算法的骨架固定下来,只允许对局部步骤进行替换。

用一个通俗的比喻:你把手机送修,如果只是屏幕坏了,官方只会换屏幕,其他零件不动,这相当于模板方法模式——修手机的流程骨架不变,只有显示屏这一步被替换;如果你的手机已经老旧不堪,维修师傅建议你直接换一台新手机,这就是策略模式——整个手机(算法)整体替换,只留下相同的"能用"目标。

在代码实现上,策略模式的客户端持有策略接口,要在运行时动态切换整个策略对象;而模板方法模式的客户端拿到的子类对象,它的算法骨架和局部实现是编译期就绑定在一起的,只能在编译期决定使用哪种子类。两者之间有非常明确的服务边界。

这个区别直接决定了使用场景。如果算法整体有多套不同的完整实现,未来的变化方向是"整体换新",策略模式更合适;如果算法骨架是稳定的,变化只发生在特定的几个步骤上,模板方法模式更合适。

4.2 选型判据与实战心得

我总结了一套自己在做技术选型时的判断标准,你可以直接拿去套用。

第一,看变化粒度。变化是一个方法的完整逻辑,还是一个流程中的某个步骤?如果是前者,优先策略模式;如果是后者,优先模板方法。

第二,看骨架的稳定性。你确定的那个流程,未来会不会被推翻重构?如果骨架本身也经常变,那模板方法就不太合适,因为父类一变,所有子类都会受影响。正所谓"父类动一动,子类抖三抖"。

第三,看是否需要在运行时切换。策略模式天然支持运行时切换策略,模板方法如果想运行时切换,只能通过创建不同子类对象间接实现。如果你的业务存在"同一个流程、同一个入口、但运行时要根据配置切换行为"的需求,策略模式组合模板方法会更灵活。

第四,看组合优于继承在你团队里的接受度。继承层级太深,代码会越来越难读。一个模板方法如果下面挂了五六层子类继承,每次加一个特性都要担心影响面。这时候你要是能把它改造成"策略模式 + 模板方法合体",即骨架做成策略类,步骤通过函数接口注入,整个结构会扁平很多。

我在实际项目里最常用的姿势是:用模板方法定义流程,用策略模式处理流程中某一步的算法变体,也就是把两者组合起来用。前面报表导出中心就是这么做的,模板方法管流程,渲染器管格式算法,两个模式配合得非常好。设计模式之间不是互斥的,组合使用往往能发挥更大的价值。

5. 踩过的坑与实用建议

5.1 这些坑我基本都踩过

第一个坑:模板方法在构造函数里被调用。父类在构造器中就调用模板方法或某个原语方法,会导致子类还没初始化完成就执行了实例方法,结果访问到的是 null 或者默认值。比如在某次项目里,我在 AbstractReportExporter 的构造函数里调用了getMaxExportRows(),其中某个子类覆写了这个方法且依赖自己的成员变量 maxRows,结果每次创建该子类的时候 maxRows 都为 null。排查了很久才发现问题根源。解决方案是把这些方法的调用时机挪出构造函数,或者在调用前明确初始化顺序。

第二个坑:子类覆盖了整个模板方法。这个现象出在"模板方法没有加 final"的代码里。稍微资深一点的开发都知道模板方法模式要求模板方法不允许覆写,但新手很容易忽略,测试同学一发现某个子类行为不符合预期,就直接在子类里覆写了 export 方法,模板方法模式立刻变成普通的继承覆盖,骨架的约束力完全丧失。我现在的代码规范里明确要求模板方法一律 final。

第三个坑:把钩子做成了抽象方法。有些开发对"钩子"理解不到位,写出来的钩子是 abstract,逼着每个子类都必须实现它。钩子的意义恰恰在于"可选参与",它应该提供一个默认实现,让子类可以置之不理。如果一个"钩子"所有子类都要覆写才能正常工作,那它其实是一个原语操作,应该用抽象方法表达,用错了抽象特征会让子类增加大量无意义的重复代码。

第四个坑:对原语操作强行增加参数。模板方法模式里的原语方法,参数列表应该保持精简,只传该方法真正需要的数据。我第一次设计报表导出器时给 queryData 传了一个巨大的上下文对象,里面既有查询参数又有用户信息又有格式信息,结果所有原语操作的签名都被污染了,一个参数改动要连带改三个子类。后来我改成每个原语方法只接收最小必要参数,改动成本大幅下降。

第五个坑:父类实现了太多非通用逻辑。有些通用的步骤确实应该下沉到父类,但如果一个方法只被一半的子类使用,那把它放父类就会污染另一半子类。遇到这种情况,应该拆成更细粒度的钩子,或者把非通用逻辑放回子类。判断标准很简单:父类方法至少要被大多数子类用到,且实现逻辑对所有子类无副作用。

5.2 几个让模板方法更优雅的习惯

除了避开上面的坑,我还在实践中积累了一些能让模板方法模式用起来更顺手的小习惯,这里一并整理给你。

第一个习惯:给原语操作命名时带上明确的语义提示。原语操作是设计给子类覆写的,命名要能让人一眼看懂"这一步是干什么的"。比如用buildQueryParam而不是step1,用processData而不是doSomething。骨架方法里每个步骤的注释也应当保留,后续看代码的人能通过注释快速还原整个流程。

第二个习惯:原语操作的可见性设置为 protected,暴露范围越小越好。父类的模板方法是 public final,原语操作是 protected abstract 或 protected 钩子,这样既保证外部可调用入口,又防止业务方误用内部步骤。

第三个习惯:在模板方法里使用日志或调试输出时,加上步骤编号。比如log("Step 3/9: processData")。这样当生产环境出一个问题时,能从日志流水快速定位到流程到哪一步炸的。这个习惯在流程较为复杂的模板方法里特别有用。

第四个习惯:不要让模板方法做得太长。如果流程超过十个步骤,我会先把部分步骤合并成子流程,比如把"渲染 + 上传 + 写日志"合并为一个persistExportResult方法,再把细节放到私有方法里。模板方法本身保持清新简洁,逻辑越直接,越不容易在后期被改坏。

最后再分享一点个人体会

设计模式学到最后,工具价值反而其次,它真正提升的是你识别"变化点"的能力。模板方法模式让我在拿到任何一段流程化业务时,都会下意识问三个问题:哪些步骤是稳定不变的、哪些步骤是允许变化的、变化是否可选。这三个问题的答案基本决定了整个类结构怎么搭。模板方法模式只是这个思路的表达形式之一,但它给了我最标准的切入点,也是我在多次重构中收益最稳定的模式之一。后续如果你的业务开始出现"同流程多实现"的苗头,可以试试先画流程骨架,再把每个变化点标出来,用模板方法把它落成代码,你会感受到代码结构一下子稳定下来的清爽感觉。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 9:01:02

Label Studio工程化:从画框工具到可插拔数据标注平台

1. 先撕掉“画框软件”的标签&#xff1a;把标注工具当数据基础设施来理解我刚接触 Label Studio 那会儿&#xff0c;第一反应也是“这工具画框挺好用的”。界面干净、拖拽顺手、快捷键舒服&#xff0c;团队里的小伙子给他套了个分类模型&#xff0c;一晚上能刷几百张图&#x…

作者头像 李华
网站建设 2026/10/20 9:00:43

从AI发展阶段到NLP核心应用:一文理清自然语言处理技术脉络与落地实践

人工智能这几年被聊得很多&#xff0c;但真正能把“AI到底发展到哪一步了”和“自然语言处理究竟在解决什么问题”这两件事讲清楚的资料其实不多。尤其对于刚入行的开发者、产品经理&#xff0c;或者只是想搞清楚技术边界的朋友来说&#xff0c;网上信息太散&#xff0c;要么是…

作者头像 李华
网站建设 2026/10/20 9:00:17

AI前沿技术日更简报全攻略:从信源筛选到内容运营的实战经验

做AI前沿技术日更简报这个项目&#xff0c;我坚持了几个月&#xff0c;每天雷打不动地筛选、精读、写作、发布。有人问我最难的是什么&#xff0c;我通常会回一句&#xff1a;“最难的不是写不出来&#xff0c;而是每天都有足够多值得写的东西&#xff0c;同时又有足够多不值得…

作者头像 李华
网站建设 2026/10/20 8:59:47

展锐平台PDAF调试全解析:从原理到工程实践

1. 项目概述与调试背景1.1 为什么PDAF调试是摄像头开发里的硬骨头接触展锐平台的PDAF调试&#xff0c;最早是在一个偏中端的项目上。当时客户要求前摄支持PDAF对焦&#xff0c;拿到样机实测发现对焦速度倒是不慢&#xff0c;但光线稍微暗一点就会频繁拉风箱&#xff0c;对焦成功…

作者头像 李华
网站建设 2026/10/20 8:59:29

PosPool:极简位置池化如何统一主流点云算子

这期论文精读想聊一个看着很朴素、后劲却很大的点云算子工作。它来自 ECCV 2020&#xff0c;叫 PosPool——按字面翻译就是“位置池化”。当时大家都在往点云算子里堆更精巧的模块、更复杂的卷积变体&#xff0c;结果这篇论文偏偏反着来&#xff0c;用“一个几乎没有额外可学习…

作者头像 李华
网站建设 2026/10/20 8:59:10

无刷直流电机MRAS自适应控制:惯量突变5倍下的转速稳定仿真

做电机控制应该都遇到过这种经历&#xff1a;台架上废了半天劲调好的PI参数&#xff0c;一接上真实负载&#xff0c;转速马上开始来回荡&#xff0c;过冲大得让人怀疑模型是不是写错了。其实问题往往不在调试&#xff0c;而是转动惯量变了。这几天做了一套无刷直流电机的MRAS模…

作者头像 李华