设计模式详解-模板方法模式

设计模式详解:模板方法模式

一、模式概述

模板方法模式(Template Method Pattern)是行为型设计模式中最具框架设计价值的模式,其核心意图在于定义一个操作中的算法骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的前提下,重新定义算法的某些特定步骤,从而实现代码复用与扩展的优雅平衡。

模板方法模式的命名直接揭示了其本质——"模板"即固定的框架、可复用的范式。在出版领域,书籍模板规定了封面、目录、正文、附录的结构,作者只需填充具体内容;在软件开发中,JUnit的setUp-test-tearDown、Spring的init-service-destroy、Servlet的doGet/doPost,均是模板方法模式的经典体现。这些框架定义了生命周期或处理流程的骨架,开发者通过覆盖特定钩子方法,插入自定义逻辑,而无需关心整体流程的控制。

模板方法模式的深层价值在于**“好莱坞原则”(Don’t call us, we’ll call you)**的实现。父类作为高层组件控制流程,子类作为低层组件提供具体实现,但调用方向反转——不是子类调用父类,而是父类调用子类。这一原则解耦了高层算法与低层实现,使得框架代码能够稳定复用,而扩展点保持灵活开放。

二、模式结构

模板方法模式包含两个核心角色,形成清晰的层次协作:

抽象类(AbstractClass):定义抽象的原语操作(primitive operations),具体的子类将重定义这些操作以实现一个算法的各步骤;实现一个模板方法,定义算法的骨架,该模板方法不仅调用原语操作,也调用定义在AbstractClass中的其他操作。

具体类(ConcreteClass):实现原语操作以完成算法中与特定子类相关的步骤。

模板方法模式的关键机制包括:

  • 模板方法(Template Method):声明为final,防止子类篡改算法骨架。
  • 原语操作(Primitive Operations):抽象或保护的方法,子类必须或可选覆盖。
  • 钩子方法(Hook Methods):提供默认空实现,子类可选择性覆盖以插入扩展点。
  • 具体方法(Concrete Methods):在抽象类中完整实现,子类直接继承使用。

三、深度案例:企业级数据处理管道引擎

以下展示一个真实场景下的模板方法模式应用——金融数据平台的批量数据处理引擎,支持ETL、风控计算、报表生成等多种处理场景,每种场景共享相同的处理骨架,但具体实现各异。

3.1 抽象模板:数据处理管道

java
/** * 抽象类:数据处理管道模板 * 定义所有数据处理场景的通用骨架 */ public abstract class DataProcessingPipeline<T extends ProcessingContext, R extends ProcessingResult> { // 依赖注入的基础设施 protected final PipelineMetrics metrics; protected final EventPublisher eventPublisher; protected final ErrorHandler errorHandler; protected final CheckpointManager checkpointManager; protected DataProcessingPipeline(PipelineMetrics metrics, EventPublisher eventPublisher, ErrorHandler errorHandler, CheckpointManager checkpointManager) { this.metrics = metrics; this.eventPublisher = eventPublisher; this.errorHandler = errorHandler; this.checkpointManager = checkpointManager; } /** * 模板方法:定义数据处理的核心骨架 * 声明为final,防止子类篡改流程 */ public final R execute(T context) { String pipelineId = generatePipelineId(); context.setPipelineId(pipelineId); long startTime = System.currentTimeMillis(); try { // 1. 前置校验(钩子,可选覆盖) if (!preValidate(context)) { return createValidationFailureResult(context); } // 2. 资源准备(抽象方法,必须实现) ProcessingResource resource = prepareResource(context); // 3. 数据加载(抽象方法,必须实现) DataSource<T> dataSource = loadData(context); // 4. 处理前钩子(钩子,可选覆盖) beforeProcess(context, dataSource); // 5. 核心处理循环(具体方法,通用实现) ProcessSummary summary = processLoop(context, dataSource, resource); // 6. 处理后钩子(钩子,可选覆盖) afterProcess(context, summary); // 7. 结果持久化(抽象方法,必须实现) R result = persistResult(context, summary); // 8. 清理资源(具体方法,通用实现,可覆盖) cleanup(context, resource); // 9. 发布成功事件 publishSuccessEvent(context, result, System.currentTimeMillis() - startTime); metrics.recordSuccess(getPipelineType(), System.currentTimeMillis() - startTime); return result; } catch (PipelineRetryableException e) { // 可重试异常:记录检查点,抛出由调度器重试 checkpointManager.saveCheckpoint(pipelineId, context, e.getProgress()); metrics.recordRetryable(getPipelineType(), e); throw e; } catch (PipelineFatalException e) { // 致命异常:记录失败,发送告警 errorHandler.handleFatal(e, context); metrics.recordFatal(getPipelineType(), e); return createFatalFailureResult(context, e); } catch (Exception e) { // 未预期异常:转换后处理 PipelineFatalException wrapped = new PipelineFatalException( "未预期的处理异常", e, context); return handleUnexpectedException(context, wrapped); } } // ========== 抽象方法:子类必须实现 ========== /** * 获取管道类型标识 */ protected abstract String getPipelineType(); /** * 准备处理资源(连接池、计算引擎实例、临时存储等) */ protected abstract ProcessingResource prepareResource(T context); /** * 加载数据源 */ protected abstract DataSource<T> loadData(T context); /** * 单条数据处理(在processLoop中被调用) */ protected abstract ProcessedRecord processRecord(T context, RawRecord record, ProcessingResource resource); /** * 持久化处理结果 */ protected abstract R persistResult(T context, ProcessSummary summary); // ========== 钩子方法:子类可选覆盖 ========== /** * 前置校验钩子 */ protected boolean preValidate(T context) { return true; // 默认通过 } /** * 处理前钩子 */ protected void beforeProcess(T context, DataSource<T> dataSource) { // 默认空实现 } /** * 处理后钩子 */ protected void afterProcess(T context, ProcessSummary summary) { // 默认空实现 } /** * 异常处理钩子 */ protected R handleUnexpectedException(T context, PipelineFatalException e) { errorHandler.handleFatal(e, context); return createFatalFailureResult(context, e); } // ========== 具体方法:通用实现,子类可直接继承 ========== /** * 核心处理循环:通用的分批、并发、容错逻辑 */ protected ProcessSummary processLoop(T context, DataSource<T> dataSource, ProcessingResource resource) { ProcessSummary summary = new ProcessSummary(); List<ProcessedRecord> batch = new ArrayList<>(context.getBatchSize()); try (RecordStream stream = dataSource.openStream()) { while (stream.hasNext()) { RawRecord record = stream.next(); summary.incrementTotal(); try { // 单条处理(调用子类实现) ProcessedRecord processed = processRecord(context, record, resource); batch.add(processed); summary.incrementSuccess(); } catch (RecordSkippableException e) { // 可跳过异常:记录日志,继续 summary.addSkipped(record.getId(), e.getReason()); metrics.recordSkipped(getPipelineType()); } catch (RecordRetryableException e) { // 单条重试 ProcessedRecord processed = retryRecord(context, record, resource, e); if (processed != null) { batch.add(processed); summary.incrementSuccess(); } else { summary.addFailed(record.getId(), "重试耗尽"); } } catch (Exception e) { // 单条失败,根据策略决定 if (context.isFailFast()) { throw new PipelineFatalException("快速失败模式:单条处理异常", e); } summary.addFailed(record.getId(), e.getMessage()); metrics.recordSingleFailure(getPipelineType()); } // 批量提交 if (batch.size() >= context.getBatchSize()) { flushBatch(batch, resource); checkpointManager.updateProgress(context.getPipelineId(), stream.getProgress()); batch.clear(); } } // 剩余数据提交 if (!batch.isEmpty()) { flushBatch(batch, resource); } } return summary; } /** * 单条重试逻辑 */ protected ProcessedRecord retryRecord(T context, RawRecord record, ProcessingResource resource, RecordRetryableException originalError) { for (int attempt = 1; attempt <= context.getMaxRetries(); attempt++) { try { Thread.sleep(context.getRetryIntervalMs() * attempt); // 指数退避 return processRecord(context, record, resource); } catch (RecordRetryableException e) { // 继续重试 } catch (InterruptedException e) { Thread.currentThread().interrupt(); return null; } } return null; } /** * 批量刷新 */ protected void flushBatch(List<ProcessedRecord> batch, ProcessingResource resource) { resource.getBatchWriter().write(batch); } /** * 资源清理 */ protected void cleanup(T context, ProcessingResource resource) { if (resource != null) { resource.close(); } } /** * 生成管道ID */ protected String generatePipelineId() { return getPipelineType() + "-" + UUID.randomUUID().toString().substring(0, 8); } /** * 发布成功事件 */ protected void publishSuccessEvent(T context, R result, long durationMs) { eventPublisher.publish(PipelineCompletedEvent.builder() .pipelineId(context.getPipelineId()) .pipelineType(getPipelineType()) .durationMs(durationMs) .recordCount(result.getProcessedCount()) .build()); } // 结果工厂方法 protected abstract R createValidationFailureResult(T context); protected abstract R createFatalFailureResult(T context, PipelineFatalException e); }

3.2 具体类:ETL数据清洗管道

java
/** * 具体类:ETL数据清洗管道 * 实现数据抽取、转换、加载的完整流程 */ @Component public class EtlCleaningPipeline extends DataProcessingPipeline<EtlContext, EtlResult> { private final DataSourceManager dataSourceManager; private final TransformationEngine transformationEngine; private final DataQualityEngine qualityEngine; private final TargetRepository targetRepository; @Autowired public EtlCleaningPipeline(PipelineMetrics metrics, EventPublisher eventPublisher, ErrorHandler errorHandler, CheckpointManager checkpointManager, DataSourceManager dataSourceManager, TransformationEngine transformationEngine, DataQualityEngine qualityEngine, TargetRepository targetRepository) { super(metrics, eventPublisher, errorHandler, checkpointManager); this.dataSourceManager = dataSourceManager; this.transformationEngine = transformationEngine; this.qualityEngine = qualityEngine; this.targetRepository = targetRepository; } @Override protected String getPipelineType() { return "ETL-CLEANING"; } @Override protected boolean preValidate(EtlContext context) { // ETL特定校验:源表存在性、字段映射完整性 if (!dataSourceManager.validateSource(context.getSourceConfig())) { return false; } if (context.getFieldMappings().isEmpty()) { return false; } return super.preValidate(context); } @Override protected ProcessingResource prepareResource(EtlContext context) { // 准备Spark会话或JDBC连接池 EtlResource resource = new EtlResource(); resource.setSparkSession(SparkSession.builder() .appName("ETL-" + context.getPipelineId()) .config("spark.sql.adaptive.enabled", "true") .getOrCreate()); resource.setJdbcTemplate(dataSourceManager.getJdbcTemplate( context.getTargetConfig())); return resource; } @Override protected DataSource<EtlContext> loadData(EtlContext context) { // 根据配置选择加载方式:全量、增量、CDC switch (context.getLoadStrategy()) { case FULL: return new FullTableDataSource(context.getSourceConfig()); case INCREMENTAL: return new IncrementalDataSource(context.getSourceConfig(), context.getLastWatermark()); case CDC: return new CdcDataSource(context.getSourceConfig(), context.getBinlogPosition()); default: throw new UnsupportedOperationException("未知的加载策略"); } } @Override protected ProcessedRecord processRecord(EtlContext context, RawRecord record, ProcessingResource resource) { EtlResource etlResource = (EtlResource) resource; // 1. 数据抽取(已解序列化) Map<String, Object> rawData = record.getData(); // 2. 字段映射与转换 Map<String, Object> transformed = transformationEngine.transform( rawData, context.getFieldMappings(), context.getTransformationRules() ); // 3. 数据质量检查 QualityReport quality = qualityEngine.inspect(transformed, context.getQualityRules()); if (!quality.isPassed()) { // 质量不达标:根据策略处理 if (context.getQualityStrategy() == QualityStrategy.STRICT) { throw new RecordSkippableException("质量检查未通过: " + quality.getFailedChecks()); } // 宽松模式:标记质量等级,继续处理 transformed.put("_quality_score", quality.getScore()); } // 4. 标准化处理 transformed.put("_etl_timestamp", Instant.now()); transformed.put("_pipeline_id", context.getPipelineId()); return new ProcessedRecord(record.getId(), transformed); } @Override protected void beforeProcess(EtlContext context, DataSource<EtlContext> dataSource) { // ETL特定:创建目标表(如不存在) targetRepository.ensureTableExists(context.getTargetTable(), context.getFieldMappings()); // 注册UDF EtlResource resource = (EtlResource) context.getResource(); resource.getSparkSession().udf().register("customHash", new CustomHashUdf(), DataTypes.StringType); } @Override protected EtlResult persistResult(EtlContext context, ProcessSummary summary) { // 更新水印 if (context.getLoadStrategy() == LoadStrategy.INCREMENTAL) { targetRepository.updateWatermark(context.getTargetTable(), context.getCurrentWatermark()); } // 生成质量报告 QualityReport aggregateQuality = qualityEngine.aggregate( context.getPipelineId()); return EtlResult.builder() .pipelineId(context.getPipelineId()) .processedCount(summary.getTotal()) .successCount(summary.getSuccess()) .skippedCount(summary.getSkipped().size()) .failedCount(summary.getFailed().size()) .qualityReport(aggregateQuality) .targetTable(context.getTargetTable()) .build(); } @Override protected EtlResult createValidationFailureResult(EtlContext context) { return EtlResult.builder() .pipelineId(context.getPipelineId()) .status(EtlStatus.VALIDATION_FAILED) .build(); } @Override protected EtlResult createFatalFailureResult(EtlContext context, PipelineFatalException e) { return EtlResult.builder() .pipelineId(context.getPipelineId()) .status(EtlStatus.FAILED) .errorMessage(e.getMessage()) .build(); } }

3.3 具体类:风控特征计算管道

java
/** * 具体类:风控特征计算管道 * 实时计算用户/交易的风险特征 */ @Component public class RiskFeaturePipeline extends DataProcessingPipeline<RiskContext, RiskResult> { private final FeatureRepository featureRepository; private final FeatureCalculatorRegistry calculatorRegistry; private final RuleEngine ruleEngine; private final RedisTemplate<String, String> redisTemplate; @Autowired public RiskFeaturePipeline(PipelineMetrics metrics, EventPublisher eventPublisher, ErrorHandler errorHandler, CheckpointManager checkpointManager, FeatureRepository featureRepository, FeatureCalculatorRegistry calculatorRegistry, RuleEngine ruleEngine, RedisTemplate<String, String> redisTemplate) { super(metrics, eventPublisher, errorHandler, checkpointManager); this.featureRepository = featureRepository; this.calculatorRegistry = calculatorRegistry; this.ruleEngine = ruleEngine; this.redisTemplate = redisTemplate; } @Override protected String getPipelineType() { return "RISK-FEATURE"; } @Override protected ProcessingResource prepareResource(RiskContext context) { // 预加载特征计算器 RiskResource resource = new RiskResource(); resource.setCalculators(context.getRequiredFeatures().stream() .map(calculatorRegistry::get) .collect(Collectors.toList())); // 预加载用户历史数据到本地缓存 resource.setUserHistory(loadUserHistory(context.getUserId())); return resource; } @Override protected DataSource<RiskContext> loadData(RiskContext context) { // 风控场景:输入是事件流而非数据表 return new EventStreamDataSource(context.getEventStream()); } @Override protected ProcessedRecord processRecord(RiskContext context, RawRecord record, ProcessingResource resource) { RiskEvent event = (RiskEvent) record.getData(); RiskResource riskResource = (RiskResource) resource; // 1. 计算各项特征 Map<String, Object> features = new HashMap<>(); for (FeatureCalculator calculator : riskResource.getCalculators()) { Object value = calculator.calculate(event, riskResource.getUserHistory()); features.put(calculator.getFeatureName(), value); } // 2. 特征交叉(组合特征) Map<String, Object> crossedFeatures = generateCrossFeatures(features); features.putAll(crossedFeatures); // 3. 规则命中检查 List<RuleHit> ruleHits = ruleEngine.evaluate(features, context.getActiveRules()); // 4. 更新用户历史(内存中) riskResource.getUserHistory().update(event, features); // 5. 写入特征存储(实时 + 离线) featureRepository.writeRealtime(context.getUserId(), event.getEventId(), features); return new ProcessedRecord(event.getEventId(), Map.of( "features", features, "ruleHits", ruleHits, "riskScore", aggregateRiskScore(ruleHits) )); } @Override protected void afterProcess(RiskContext context, ProcessSummary summary) { // 批量写入离线特征库(T+1使用) featureRepository.flushOfflineBatch(context.getUserId()); // 更新Redis中的实时特征缓存 redisTemplate.opsForHash().putAll( "risk:features:" + context.getUserId(), serializeFeatures(summary.getLastBatch()) ); } @Override protected RiskResult persistResult(RiskContext context, ProcessSummary summary) { return RiskResult.builder() .pipelineId(context.getPipelineId()) .userId(context.getUserId()) .processedEvents(summary.getTotal()) .featureSnapshot(featureRepository.getLatestSnapshot(context.getUserId())) .build(); } @Override protected RiskResult createValidationFailureResult(RiskContext context) { return RiskResult.builder() .pipelineId(context.getPipelineId()) .status(RiskStatus.INVALID_INPUT) .build(); } @Override protected RiskResult createFatalFailureResult(RiskContext context, PipelineFatalException e) { // 风控失败:降级返回基础特征 return RiskResult.builder() .pipelineId(context.getPipelineId()) .status(RiskStatus.DEGRADED) .fallbackFeatures(loadFallbackFeatures(context.getUserId())) .build(); } private Map<String, Object> generateCrossFeatures(Map<String, Object> baseFeatures) { // 特征交叉逻辑:如 device_fingerprint × ip_risk_score Map<String, Object> crossed = new HashMap<>(); // 实现省略... return crossed; } private double aggregateRiskScore(List<RuleHit> hits) { return hits.stream() .mapToDouble(RuleHit::getScore) .max() .orElse(0.0); } }

3.4 具体类:监管报表生成管道

java
/** * 具体类:监管报表生成管道 * 满足央行、银保监会的报送要求 */ @Component public class RegulatoryReportPipeline extends DataProcessingPipeline<ReportContext, ReportResult> { private final ReportTemplateRepository templateRepository; private final DataExtractor dataExtractor; private final ReportValidator reportValidator; private final RegulatoryGateway regulatoryGateway; @Override protected String getPipelineType() { return "REGULATORY-REPORT"; } @Override protected boolean preValidate(ReportContext context) { // 校验报送日期是否为工作日 if (!isWorkingDay(context.getReportDate())) { return false; } // 校验模板版本是否为最新 ReportTemplate template = templateRepository.getTemplate( context.getReportType(), context.getReportDate()); if (template.getVersion() != context.getExpectedTemplateVersion()) { return false; } return super.preValidate(context); } @Override protected DataSource<ReportContext> loadData(ReportContext context) { // 监管报表:从数据仓库按主题域抽取 return new WarehouseDataSource(context.getReportType().getRequiredTables(), context.getReportDate()); } @Override protected ProcessedRecord processRecord(ReportContext context, RawRecord record, ProcessingResource resource) { // 数据映射到监管标准编码 Map<String, Object> regulatoryData = mapToRegulatoryFormat( record.getData(), context.getReportType()); // 金额单位转换(元 -> 万元) convertMonetaryUnit(regulatoryData, context.getReportType().getMonetaryUnit()); // 脱敏处理 desensitizeSensitiveFields(regulatoryData, context.getReportType()); return new ProcessedRecord(record.getId(), regulatoryData); } @Override protected void afterProcess(ReportContext context, ProcessSummary summary) { // 报表特定:汇总校验 ReportTemplate template = templateRepository.getTemplate( context.getReportType(), context.getReportDate()); List<ValidationRule> balanceRules = template.getBalanceRules(); for (ValidationRule rule : balanceRules) { if (!validateBalance(summary.getAggregatedData(), rule)) { throw new PipelineFatalException("报表平衡校验未通过: " + rule.getName()); } } } @Override protected ReportResult persistResult(ReportContext context, ProcessSummary summary) { // 生成监管标准格式文件 byte[] reportFile = generateRegulatoryFile(context.getReportType(), summary.getProcessedRecords()); // 数字签名 byte[] signature = signReport(reportFile); // 报送 SubmitResult submitResult = regulatoryGateway.submit( context.getReportType(), context.getReportDate(), reportFile, signature ); return ReportResult.builder() .pipelineId(context.getPipelineId()) .reportType(context.getReportType()) .reportDate(context.getReportDate()) .submitReference(submitResult.getReference()) .status(submitResult.isAccepted() ? ReportStatus.SUBMITTED : ReportStatus.REJECTED) .build(); } // 监管特定方法... }

四、模板方法模式的高级主题

4.1 回调接口替代继承

Java 8+中,函数式接口可替代抽象类,提供更灵活的扩展机制:

java
/** * 基于回调的模板方法 * 避免继承,支持运行时组合 */ public class CallbackBasedPipeline<T, R> { private final Function<T, Boolean> preValidator; private final Function<T, DataSource<T>> dataLoader; private final BiFunction<T, RawRecord, ProcessedRecord> processor; private final BiFunction<T, ProcessSummary, R> resultPersister; public CallbackBasedPipeline(Builder<T, R> builder) { this.preValidator = builder.preValidator; this.dataLoader = builder.dataLoader; this.processor = builder.processor; this.resultPersister = builder.resultPersister; } public R execute(T context) { if (!preValidator.apply(context)) { throw new ValidationException(); } DataSource<T> dataSource = dataLoader.apply(context); ProcessSummary summary = processLoop(context, dataSource); return resultPersister.apply(context, summary); } // Builder模式... } // 使用Lambda配置管道 CallbackBasedPipeline<EtlContext, EtlResult> pipeline = new CallbackBasedPipeline.Builder<EtlContext, EtlResult>() .preValidator(ctx -> ctx.getFieldMappings() != null) .dataLoader(ctx -> new FullTableDataSource(ctx.getSourceConfig())) .processor((ctx, record) -> { // 自定义处理逻辑 return new ProcessedRecord(record.getId(), transformed); }) .resultPersister((ctx, summary) -> { // 自定义持久化 return EtlResult.builder().build(); }) .build();

4.2 模板方法与Spring的整合

java
/** * Spring风格的模板方法:通过Bean注入扩展点 */ public abstract class SpringAwarePipeline<T extends ProcessingContext, R extends ProcessingResult> implements ApplicationContextAware { private ApplicationContext applicationContext; // 注入扩展点而非强制覆盖 @Autowired(required = false) private List<PipelineInterceptor<T, R>> interceptors = new ArrayList<>(); @Override public void setApplicationContext(ApplicationContext context) { this.applicationContext = context; } public final R execute(T context) { // 执行拦截器链 PipelineChain<T, R> chain = new DefaultPipelineChain<>(this, interceptors); return chain.proceed(context); } // 抽象方法定义扩展点... }

五、模板方法与相关模式的辨析

模板方法 vs 策略模式:模板方法定义算法骨架,子类覆盖步骤;策略模式封装 interchangeable 的算法族,客户端选择策略。模板方法是"白盒复用"(知道骨架),策略是"黑盒复用"(仅知道接口)。

模板方法 vs 工厂方法:工厂方法是模板方法的特殊形式,模板方法中的某一步骤专门用于创建对象。二者常结合使用:模板方法定义流程,工厂方法创建流程中需要的对象。

模板方法 vs 建造者模式:建造者逐步构建复杂对象,模板方法定义操作算法。二者可结合:模板方法的某步骤使用建造者创建对象。

六、设计陷阱与规避策略

陷阱一:模板方法过于庞大

当模板方法包含数十个步骤时,难以理解和维护。解决方案:提取子模板方法,或将部分步骤委托给策略对象。

陷阱二:子类泛滥

每个变体都需要新子类,导致类爆炸。解决方案:结合策略模式,将部分变化点提取为可配置的策略对象。

陷阱三:钩子方法滥用

过多钩子使子类难以决定覆盖哪些。解决方案:明确区分必须实现的抽象方法与可选的钩子,文档化钩子的使用场景。

七、结语

模板方法模式是框架设计的基石,它将流程控制与具体实现解耦,使高层算法稳定复用,低层扩展灵活开放。在数据管道、生命周期管理、工作流引擎等场景中,模板方法模式是不可或缺的组织原则。理解其"好莱坞原则"的深层哲学,掌握抽象方法、钩子方法、具体方法的协同设计,警惕继承僵化与模板膨胀的陷阱,是构建可扩展框架的核心能力。模板方法模式不仅是一种代码结构,更是一种设计思维——定义不变的骨架,拥抱变化的细节

返回知识中心