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

你写的代码一半都是重复逻辑,模板方法能帮你省掉

你写的代码一半都是重复逻辑,模板方法能帮你省掉

写过 JDBC 的人应该都有这段记忆:

Connection conn = null;

PreparedStatement ps = null;

ResultSet rs = null;

try {

conn = dataSource.getConnection();

ps = conn.prepareStatement(sql);

ps.setString(1, name);

rs = ps.executeQuery();

while (rs.next()) {

// 处理结果

}

} catch (SQLException e) {

e.printStackTrace();

} finally {

if (rs != null) rs.close();

if (ps != null) ps.close();

if (conn != null) conn.close();

}

这段代码你写一次,第二次写另一个查询的时候,结构几乎一模一样,只有 SQL 和结果处理不同。

这种"骨架相同、细节不同"的代码,就是模板方法模式要干掉的。

---

什么意思

模板方法的思路是:**把不变的流程固化在父类里,把变化的部分留给子类去实现。**

"获取连接→准备语句→执行→处理结果→关闭资源"这个流程是不变的,变化的是 SQL 和结果处理。

public abstract class JdbcTemplate {

public final T execute(String sql) {

Connection conn = null;

PreparedStatement ps = null;

ResultSet rs = null;

try {

conn = dataSource.getConnection();

ps = conn.prepareStatement(sql);

setParameters(ps); // 子类实现:设置参数

rs = ps.executeQuery();

return handleResult(rs); // 子类实现:处理结果

} catch (SQLException e) {

throw new RuntimeException(e);

} finally {

closeQuietly(rs, ps, conn);

}

}

protected abstract void setParameters(PreparedStatement ps) throws SQLException;

protected abstract T handleResult(ResultSet rs) throws SQLException;

}

子类只需要关注自己的那部分:

public class FindUserById extends JdbcTemplate {

private final Long userId;

public FindUserById(DataSource ds, Long userId) {

super(ds);

this.userId = userId;

}

@Override

protected void setParameters(PreparedStatement ps) throws SQLException {

ps.setLong(1, userId);

}

@Override

protected User handleResult(ResultSet rs) throws SQLException {

if (rs.next()) {

return new User(rs.getLong("id"), rs.getString("name"));

}

return null;

}

}

其实 Spring 的JdbcTemplate就是这个思路的工业级实现。只不过 Spring 用回调函数代替了继承,更灵活一些。

---

Spring 里的模板方法

你可能没意识到,你每天都在用模板方法模式。Spring 的大量基类设计都基于这个模式。

AbstractTransactionalDataSourcePlatformTransactionManager——光看名字就知道它是个抽象基类,里面的doBegin()doCommit()doRollback()都是留给子类实现的钩子方法。DataSourceTransactionManagerJtaTransactionManager分别继承了它,实现了不同的资源管理方式。

Spring Batch 里更明显:

public abstract class ItemProcessor {

public final O process(I item) throws Exception {

O result = doProcess(item); // 子类实现

if (result == null) {

throw new SkipItemException("处理结果为空,跳过");

}

return result;

}

protected abstract O doProcess(I item) throws Exception;

}

process是模板方法,doProcess是钩子。模板方法用final修饰,防止子类覆盖流程,保证骨架不被破坏。

---

用回调替代继承

继承是模板方法的经典实现,但继承有硬限制——Java 只能单继承,你的类如果已经有父类了,就没法再用模板方法。

更灵活的方式是用回调(函数式接口),本质上还是模板方法的思路——固定骨架,变化部分注入:

public class TemplateRunner {

public T runWithTransaction(ConnectionCallback action) {

Connection conn = null;

try {

conn = dataSource.getConnection();

conn.setAutoCommit(false);

T result = action.doInConnection(conn); // 回调:变化的部分

conn.commit();

return result;

} catch (Exception e) {

conn.rollback();

throw new RuntimeException(e);

} finally {

conn.close();

}

}

}

@FunctionalInterface

public interface ConnectionCallback {

T doInConnection(Connection conn) throws Exception;

}

用的时候:

User user = runner.runWithTransaction(conn -> {

PreparedStatement ps = conn.prepareStatement("SELECT * FROM user WHERE id = ?");

ps.setLong(1, userId);

ResultSet rs = ps.executeQuery();

return rs.next() ? new User(rs.getLong("id"), rs.getString("name")) : null;

});

这其实就是 SpringJdbcTemplate.query()的思路。回调 + 模板方法,比继承更灵活,Lambda 写起来也比子类清爽。

---

什么时候该用

模板方法模式适合这类场景:

- 多个类有共同的执行流程,只有个别步骤不同

- 你想固定执行顺序,不允许子类改变骨架

- 公共逻辑应该在父类统一处理(比如异常、日志、事务)

如果你发现自己在多个子类里写了一模一样的代码,而且这些代码的执行顺序也相同,那就可以考虑抽模板方法。

---

什么时候不该用

骨架不固定、流程经常变化的场景,模板方法反而碍事。每次流程一变你都要改父类,所有子类可能都要受影响。

还有一种情况:不同子类之间的差异太大,模板方法里定义的钩子太多(超过 5 个),说明你的抽象不合适。这时候不如把逻辑拆开,每个子类自己决定流程。

---

我在做一个用卡皮巴拉讲 23 个设计模式的微信小程序「**爪爪代码冒险记**」,漫画 + 答题闯关,比翻书有意思。微信搜「爪爪代码冒险记」就能找到。

http://www.jsqmd.com/news/943588/

相关文章:

  • 萍乡市地区2026年权威甄选:黄金回收白银铂金回收优质门店 TOP5 含详细电话 - 诚金汇钻回收公司
  • 不只是聊天记录:一次对‘内部通联软件’的深度取证剖析(含包名、权限、服务器抓包全流程)
  • 《自适应滤波原理》第4版17章课后题逐题解析+MATLAB可运行代码(含LMS/RLS/卡尔曼仿真)
  • 告别数据“裸奔”危机:网络加密机如何打造端到端安全传输隧道
  • 终极指南:如何使用Ludusavi免费备份你的PC游戏存档,彻底告别进度丢失!
  • 从电路设计到完整项目:Instructables创客平台实战指南
  • 十大口碑轿车托运平台推荐:安全高效的跨城运车选择 - 品牌评测官
  • 从概念到产品:基于快马平台快速开发peqw4tyafk数据分析仪表盘
  • 微软发布MAI-Code-1-Flash:不拼参数,靠高效实用挑战编程AI赛道
  • 南平市地区2026年权威甄选:黄金回收白银铂金回收优质门店 TOP5 含详细电话 - 诚金汇钻回收公司
  • 2026年常州翡翠回收哪家好?合扬本地资深门店,无套路机构更推荐 - 合扬奢侈品交易中心
  • 从零到一:用DeepXDE解决传统数值方法头疼的微分方程问题
  • 抖音下载终极指南:5分钟掌握高效批量下载技巧
  • ChanlunX:3分钟让通达信自动画缠论中枢的终极解决方案
  • MAA_Punish:模块化智能自动化框架的架构设计与技术实现
  • 十堰市地区2026年权威甄选:黄金回收白银铂金回收优质门店 TOP5 含详细电话 - 诚金汇钻回收公司
  • Rainmeter插件性能优化:如何打造高效桌面监控系统
  • 5分钟掌握浏览器音乐解密:解锁10+主流平台加密格式的终极指南
  • 杭州宝珀腕表成了 “过山车”?杭州表主必看:走时忽快忽慢不是玄学,擒纵系统异常才是真凶 - 亨得利官方维修中心
  • 天津昊力复合钢管制造:临汾水涂塑复合钢管制造厂家推荐 - LYL仔仔
  • 基于树莓派与OpenCV的运动追踪系统:从视觉感知到物理控制
  • 7-Zip下载保姆级安装图文教程(全网最详细)【附官方安装包】
  • 5分钟掌握PUBG压枪技巧:罗技鼠标宏终极配置指南
  • 免费开源图片去重神器:3分钟学会用AntiDupl.NET告别重复照片烦恼
  • 5个高级技巧:在yuzu模拟器中实现游戏内存修改与参数调整
  • 【Flutter】Flutter 组件 ③ ( 组件位置设置 | 相对定位 | 绝对定位 | 位置偏移定位 | FractionallySizedBox 百分比定位 | alignment 百分比 )
  • Arduino音频工具终极指南:嵌入式音频开发的完整解决方案
  • Coyote框架:系统性探索异步并发缺陷,构建高可靠分布式系统
  • 基于树莓派与FFT算法的智能报警中继系统设计与实现
  • ESP-01编程器PCB设计:从电路原理到固件烧录的完整实践