设计模式-模板方法模式详解
模板方法模式详解
目录
- 1. 模板方法模式简介
- 2. 核心流程
- 3. 重难点分析
- 4. Spring中的源码分析
- 5. 面试高频点
1. 模板方法模式简介
1.1 定义
模板方法模式(Template Method Pattern)是一种行为型设计模式,它定义了一个算法的骨架,将一些步骤延迟到子类中实现。模板方法使得子类可以不改变算法的结构,就可以重定义算法的某些特定步骤。
1.2 核心思想
- 算法骨架固定:定义算法的主要流程,不允许子类改变
- 步骤可定制:将算法中的某些步骤抽象出来,由子类实现
- 代码复用:避免重复代码,提高代码复用性
- 扩展性:通过子类扩展新的实现方式
1.3 适用场景
- 多个类有相同的算法结构,但具体实现不同
- 需要控制子类扩展的范围
- 需要提取公共行为到父类中
- 框架设计中的钩子方法(Hook Method)
1.4 模板方法模式结构
类图结构
核心组件
- AbstractClass(抽象类):定义模板方法和抽象方法
- ConcreteClass(具体类):实现抽象方法
- Template Method(模板方法):定义算法骨架
- Primitive Operations(原语操作):抽象方法,由子类实现
- Hook Methods(钩子方法):可选方法,子类可以重写
1.5 模板方法模式分类
基本模板方法模式
// 抽象类定义模板方法
public abstract class AbstractClass {// 模板方法,定义算法骨架public final void templateMethod() {primitiveOperation1();primitiveOperation2();concreteOperation();if (hook()) {hookOperation();}}// 抽象方法,由子类实现protected abstract void primitiveOperation1();protected abstract void primitiveOperation2();// 具体方法,在抽象类中实现protected void concreteOperation() {System.out.println("具体操作");}// 钩子方法,子类可以重写protected boolean hook() {return true;}protected void hookOperation() {System.out.println("钩子操作");}
}
带钩子的模板方法模式
// 带钩子方法的模板方法
public abstract class AbstractClassWithHooks {public final void templateMethod() {beforeOperation();doOperation();afterOperation();if (needAdditionalOperation()) {additionalOperation();}}protected abstract void doOperation();// 钩子方法protected void beforeOperation() {// 默认空实现}protected void afterOperation() {// 默认空实现}protected boolean needAdditionalOperation() {return false;}protected void additionalOperation() {// 默认空实现}
}
2. 核心流程
2.1 模板方法执行流程
2.2 模板方法设计流程
步骤1:识别算法结构
// 识别需要模板化的算法
public class AlgorithmExample {public void processData() {// 步骤1:初始化initialize();// 步骤2:验证数据validateData();// 步骤3:处理数据processData();// 步骤4:保存结果saveResult();// 步骤5:清理资源cleanup();}private void initialize() { /* 具体实现 */ }private void validateData() { /* 具体实现 */ }private void processData() { /* 具体实现 */ }private void saveResult() { /* 具体实现 */ }private void cleanup() { /* 具体实现 */ }
}
步骤2:提取抽象类
// 提取抽象类,定义模板方法
public abstract class DataProcessor {// 模板方法,定义算法骨架public final void processData() {initialize();validateData();doProcessData();saveResult();cleanup();}// 具体方法,所有子类共享protected void initialize() {System.out.println("初始化数据处理器");}protected void validateData() {System.out.println("验证数据格式");}protected void saveResult() {System.out.println("保存处理结果");}protected void cleanup() {System.out.println("清理资源");}// 抽象方法,由子类实现protected abstract void doProcessData();
}
步骤3:实现具体类
// 实现具体的数据处理器
public class XmlDataProcessor extends DataProcessor {@Overrideprotected void doProcessData() {System.out.println("处理XML数据");}
}public class JsonDataProcessor extends DataProcessor {@Overrideprotected void doProcessData() {System.out.println("处理JSON数据");}
}public class CsvDataProcessor extends DataProcessor {@Overrideprotected void doProcessData() {System.out.println("处理CSV数据");}
}
步骤4:客户端调用
// 客户端使用模板方法
public class Client {public static void main(String[] args) {DataProcessor xmlProcessor = new XmlDataProcessor();xmlProcessor.processData();DataProcessor jsonProcessor = new JsonDataProcessor();jsonProcessor.processData();DataProcessor csvProcessor = new CsvDataProcessor();csvProcessor.processData();}
}
2.3 钩子方法使用流程
钩子方法示例
// 带钩子方法的抽象类
public abstract class AbstractProcessor {public final void process() {beforeProcess();doProcess();afterProcess();// 钩子方法:是否需要额外处理if (needAdditionalProcess()) {additionalProcess();}// 钩子方法:是否需要日志记录if (needLogging()) {logProcess();}}protected abstract void doProcess();// 钩子方法,子类可以重写protected void beforeProcess() {System.out.println("默认前置处理");}protected void afterProcess() {System.out.println("默认后置处理");}protected boolean needAdditionalProcess() {return false;}protected void additionalProcess() {System.out.println("默认额外处理");}protected boolean needLogging() {return true;}protected void logProcess() {System.out.println("记录处理日志");}
}
3. 重难点分析
3.1 重难点一:模板方法的设计原则
难点分析
- 算法稳定性:模板方法定义的算法骨架不能被子类改变
- 扩展性平衡:既要保证算法稳定,又要允许子类扩展
- 方法粒度:如何合理划分抽象方法和具体方法
解决方案
// 良好的模板方法设计
public abstract class AbstractTemplate {// 模板方法使用final,防止子类重写public final void templateMethod() {// 1. 固定的前置处理beforeProcess();// 2. 核心业务逻辑,由子类实现doProcess();// 3. 固定的后置处理afterProcess();// 4. 可选的钩子方法if (needAdditionalProcess()) {additionalProcess();}}// 抽象方法:核心业务逻辑protected abstract void doProcess();// 具体方法:固定逻辑,子类不能改变private void beforeProcess() {System.out.println("固定前置处理");}private void afterProcess() {System.out.println("固定后置处理");}// 钩子方法:可选扩展点protected boolean needAdditionalProcess() {return false;}protected void additionalProcess() {// 默认空实现}
}
3.2 重难点二:钩子方法的设计和使用
难点分析
- 钩子方法命名:如何命名钩子方法,使其意图清晰
- 钩子方法粒度:钩子方法应该提供多大的扩展能力
- 钩子方法顺序:多个钩子方法的执行顺序如何设计
解决方案
// 钩子方法设计示例
public abstract class AbstractProcessor {public final void process() {// 主流程initialize();validate();processData();save();cleanup();// 钩子方法:可选的扩展点if (needAudit()) {audit();}if (needNotification()) {notify();}if (needMetrics()) {recordMetrics();}}protected abstract void processData();// 钩子方法:审计protected boolean needAudit() {return false;}protected void audit() {System.out.println("执行审计");}// 钩子方法:通知protected boolean needNotification() {return true; // 默认需要通知}protected void notify() {System.out.println("发送通知");}// 钩子方法:指标记录protected boolean needMetrics() {return false;}protected void recordMetrics() {System.out.println("记录指标");}
}
3.3 重难点三:模板方法模式的扩展性设计
难点分析
- 扩展点设计:如何设计合理的扩展点
- 版本兼容性:如何保证模板方法的向后兼容
- 性能考虑:钩子方法对性能的影响
解决方案
// 可扩展的模板方法设计
public abstract class AbstractProcessor {// 模板方法,支持版本控制public final void process() {process(ProcessVersion.V1);}// 支持版本控制的模板方法public final void process(ProcessVersion version) {beforeProcess(version);switch (version) {case V1:processV1();break;case V2:processV2();break;default:processV1();}afterProcess(version);}// 版本1的处理流程private void processV1() {doProcess();if (needAdditionalProcess()) {additionalProcess();}}// 版本2的处理流程private void processV2() {doProcess();if (needAdditionalProcess()) {additionalProcess();}if (needAdvancedProcess()) {advancedProcess();}}protected abstract void doProcess();// 钩子方法:版本控制protected void beforeProcess(ProcessVersion version) {// 默认实现}protected void afterProcess(ProcessVersion version) {// 默认实现}protected boolean needAdditionalProcess() {return false;}protected void additionalProcess() {// 默认实现}protected boolean needAdvancedProcess() {return false;}protected void advancedProcess() {// 默认实现}
}// 版本枚举
public enum ProcessVersion {V1, V2
}
3.4 重难点四:模板方法模式的测试
难点分析
- 抽象类测试:如何测试抽象类
- 模板方法测试:如何测试模板方法的执行流程
- 钩子方法测试:如何测试钩子方法的各种情况
解决方案
// 测试抽象类的模板方法
public class AbstractProcessorTest {@Testpublic void testTemplateMethod() {// 创建测试用的具体实现AbstractProcessor processor = new AbstractProcessor() {@Overrideprotected void doProcess() {System.out.println("测试处理");}@Overrideprotected boolean needAdditionalProcess() {return true;}@Overrideprotected void additionalProcess() {System.out.println("测试额外处理");}};// 测试模板方法processor.process();// 验证执行结果// 可以使用Mock对象验证方法调用}@Testpublic void testHookMethods() {// 测试钩子方法的不同情况AbstractProcessor processor1 = new AbstractProcessor() {@Overrideprotected void doProcess() {// 实现}@Overrideprotected boolean needAdditionalProcess() {return false; // 不执行额外处理}};AbstractProcessor processor2 = new AbstractProcessor() {@Overrideprotected void doProcess() {// 实现}@Overrideprotected boolean needAdditionalProcess() {return true; // 执行额外处理}};// 分别测试两种情况processor1.process();processor2.process();}
}
4. Spring中的源码分析
4.1 Spring中的模板方法模式应用
4.1.1 JdbcTemplate中的模板方法
// JdbcTemplate的核心模板方法
public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {// 查询模板方法public <T> T query(String sql, ResultSetExtractor<T> rse) throws DataAccessException {return query(sql, rse, (Object[]) null);}public <T> T query(String sql, ResultSetExtractor<T> rse, Object... args) throws DataAccessException {return query(sql, rse, newArgPreparedStatementSetter(args));}public <T> T query(String sql, ResultSetExtractor<T> rse, PreparedStatementSetter pss) throws DataAccessException {return query(new SimplePreparedStatementCreator(sql), pss, rse);}// 核心模板方法实现public <T> T query(PreparedStatementCreator psc, PreparedStatementSetter pss, ResultSetExtractor<T> rse) throws DataAccessException {return execute(psc, new PreparedStatementCallback<T>() {@Overridepublic T doInPreparedStatement(PreparedStatement ps) throws SQLException {ResultSet rs = null;try {if (pss != null) {pss.setValues(ps);}rs = ps.executeQuery();return rse.extractData(rs);} finally {JdbcUtils.closeResultSet(rs);}}});}// 执行模板方法public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action) throws DataAccessException {return execute(psc, action, true);}public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action, boolean closeResources) throws DataAccessException {if (logger.isDebugEnabled()) {String sql = getSql(psc);logger.debug("Executing prepared SQL statement" + (sql != null ? " [" + sql + "]" : ""));}Connection con = null;PreparedStatement ps = null;try {// 获取连接con = getConnection();// 创建PreparedStatementps = psc.createPreparedStatement(con);// 应用语句设置器applyStatementSettings(ps);// 执行回调T result = action.doInPreparedStatement(ps);// 处理警告handleWarnings(ps);return result;} catch (SQLException ex) {// 释放资源if (closeResources) {JdbcUtils.closeStatement(ps);ps = null;}// 转换异常throw translateException("PreparedStatementCallback", sql, ex);} finally {if (closeResources) {JdbcUtils.closeStatement(ps);JdbcUtils.closeConnection(con);}}}
}
4.1.2 AbstractApplicationContext中的模板方法
// AbstractApplicationContext的模板方法
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {// 刷新容器的模板方法@Overridepublic void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// 准备刷新prepareRefresh();// 获取BeanFactoryConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// 准备BeanFactoryprepareBeanFactory(beanFactory);try {// 允许BeanFactory的后处理postProcessBeanFactory(beanFactory);// 调用BeanFactoryPostProcessorsinvokeBeanFactoryPostProcessors(beanFactory);// 注册BeanPostProcessorsregisterBeanPostProcessors(beanFactory);// 初始化MessageSourceinitMessageSource();// 初始化ApplicationEventMulticasterinitApplicationEventMulticaster();// 刷新特定上下文子类onRefresh();// 检查监听器Bean并注册registerListeners();// 实例化所有非懒加载的单例BeanfinishBeanFactoryInitialization(beanFactory);// 完成刷新finishRefresh();} catch (BeansException ex) {if (logger.isWarnEnabled()) {logger.warn("Exception encountered during context initialization - " +"cancelling refresh attempt: " + ex);}// 销毁已创建的BeandestroyBeans();// 重置'active'标志cancelRefresh(ex);throw ex;} finally {// 重置公共内省缓存resetCommonCaches();}}}// 钩子方法:刷新特定上下文子类protected void onRefresh() throws BeansException {// 默认空实现,子类可以重写}// 钩子方法:准备刷新protected void prepareRefresh() {this.startupDate = System.currentTimeMillis();this.closed.set(false);this.active.set(true);if (logger.isInfoEnabled()) {logger.info("Refreshing " + this);}// 初始化属性源initPropertySources();// 验证必需属性getEnvironment().validateRequiredProperties();}
}
4.1.3 AbstractBeanFactory中的模板方法
// AbstractBeanFactory的模板方法
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {// 获取Bean的模板方法@Overridepublic Object getBean(String name) throws BeansException {return doGetBean(name, null, null, false);}// 核心模板方法protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {final String beanName = transformedBeanName(name);Object bean;// 尝试从缓存中获取单例BeanObject sharedInstance = getSingleton(beanName);if (sharedInstance != null && args == null) {bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);} else {// 检查Bean定义是否存在if (isPrototypeCurrentlyInCreation(beanName)) {throw new BeanCurrentlyInCreationException(beanName);}// 检查父BeanFactoryBeanFactory parentBeanFactory = getParentBeanFactory();if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {String nameToLookup = originalBeanName(name);if (parentBeanFactory instanceof AbstractBeanFactory) {return ((AbstractBeanFactory) parentBeanFactory).doGetBean(nameToLookup, requiredType, args, typeCheckOnly);} else if (args != null) {return (T) parentBeanFactory.getBean(nameToLookup, args);} else {return parentBeanFactory.getBean(nameToLookup, requiredType);}}if (!typeCheckOnly) {markBeanAsCreated(beanName);}try {final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);checkMergedBeanDefinition(mbd, beanName, args);// 确保依赖的Bean被初始化String[] dependsOn = mbd.getDependsOn();if (dependsOn != null) {for (String dep : dependsOn) {if (isDependent(beanName, dep)) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");}registerDependentBean(dep, beanName);try {getBean(dep);} catch (NoSuchBeanDefinitionException ex) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,"'" + beanName + "' depends on missing bean '" + dep + "'", ex);}}}// 创建Bean实例if (mbd.isSingleton()) {sharedInstance = getSingleton(beanName, () -> {try {return createBean(beanName, mbd, args);} catch (BeansException ex) {destroySingleton(beanName);throw ex;}});bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);} else if (mbd.isPrototype()) {Object prototypeInstance = null;try {beforePrototypeCreation(beanName);prototypeInstance = createBean(beanName, mbd, args);} finally {afterPrototypeCreation(beanName);}bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);} else {String scopeName = mbd.getScope();final Scope scope = this.scopes.get(scopeName);if (scope == null) {throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");}try {Object scopedInstance = scope.get(beanName, () -> {beforePrototypeCreation(beanName);try {return createBean(beanName, mbd, args);} finally {afterPrototypeCreation(beanName);}});bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);} catch (IllegalStateException ex) {throw new BeanCreationException(beanName, "Scope '" + scopeName + "' is not active for the current thread; consider " +"defining a scoped proxy for this bean if you intend to refer to it from a singleton", ex);}}} catch (BeansException ex) {if (containsSingleton(beanName)) {throw ex;}throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid bean definition with name '" + beanName + "'", ex);}}return adaptBeanInstance(name, bean, requiredType);}// 抽象方法:创建Bean实例protected abstract Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)throws BeanCreationException;// 钩子方法:原型Bean创建前protected void beforePrototypeCreation(String beanName) {// 默认空实现}// 钩子方法:原型Bean创建后protected void afterPrototypeCreation(String beanName) {// 默认空实现}
}
4.2 Spring模板方法模式的特点
4.2.1 模板方法设计特点
- 算法骨架固定:核心流程不允许子类改变
- 扩展点丰富:提供多个钩子方法供子类扩展
- 异常处理完善:统一的异常处理机制
- 资源管理规范:统一的资源获取和释放
4.2.2 钩子方法使用特点
// Spring中钩子方法的典型使用
public abstract class AbstractApplicationContext {// 钩子方法:刷新特定上下文子类protected void onRefresh() throws BeansException {// 默认空实现,子类可以重写}// 钩子方法:准备刷新protected void prepareRefresh() {// 默认实现,子类可以重写}// 钩子方法:完成刷新protected void finishRefresh() {// 默认实现,子类可以重写}
}// 具体实现类
public class GenericApplicationContext extends AbstractApplicationContext {@Overrideprotected void onRefresh() throws BeansException {// 特定上下文的刷新逻辑super.onRefresh();}@Overrideprotected void prepareRefresh() {// 特定上下文的准备逻辑super.prepareRefresh();}
}
5. 面试高频点
5.1 基础概念类问题
Q1: 什么是模板方法模式?有什么特点?
答案要点:
- 定义:定义算法骨架,将步骤延迟到子类实现
- 特点:算法结构固定,步骤可定制,代码复用
- 核心:模板方法 + 抽象方法 + 钩子方法
Q2: 模板方法模式与策略模式的区别?
答案要点:
- 模板方法模式:算法结构固定,步骤可定制
- 策略模式:算法完全可替换
- 使用场景:模板方法用于算法结构相同,策略模式用于算法完全不同
5.2 设计原理类问题
Q3: 模板方法模式的核心组件有哪些?
答案要点:
1. AbstractClass(抽象类)- templateMethod():模板方法- primitiveOperation():抽象方法- concreteOperation():具体方法- hook():钩子方法2. ConcreteClass(具体类)- 实现抽象方法- 可选重写钩子方法
Q4: 钩子方法的作用是什么?
答案要点:
- 扩展点:提供可选的扩展能力
- 控制流程:控制某些步骤是否执行
- 定制行为:允许子类定制特定行为
- 向后兼容:保证模板方法的向后兼容性
5.3 实现细节类问题
Q5: 如何设计一个好的模板方法?
答案要点:
// 设计原则
1. 模板方法使用final,防止子类重写
2. 抽象方法定义核心业务逻辑
3. 具体方法实现固定逻辑
4. 钩子方法提供扩展点
5. 合理的方法粒度划分
Q6: 模板方法模式如何保证算法稳定性?
答案要点:
- final关键字:模板方法使用final防止重写
- 访问控制:合理使用访问修饰符
- 方法设计:将可变部分抽象为方法
- 文档规范:明确说明哪些方法可以重写
5.4 Spring应用类问题
Q7: Spring中哪些地方使用了模板方法模式?
答案要点:
1. JdbcTemplate:数据库操作模板
2. AbstractApplicationContext:应用上下文刷新
3. AbstractBeanFactory:Bean创建和管理
4. AbstractTransactionalDataSource:事务管理
5. AbstractController:Web控制器基类
Q8: Spring的JdbcTemplate是如何使用模板方法模式的?
答案要点:
// 核心流程
1. 获取数据库连接
2. 创建PreparedStatement
3. 设置参数
4. 执行SQL(抽象方法)
5. 处理结果集
6. 释放资源
5.5 实际应用类问题
Q9: 在项目中如何应用模板方法模式?
答案要点:
// 应用场景
1. 数据处理流程:数据验证、处理、保存
2. 文件处理流程:读取、解析、转换、输出
3. 业务流程:审批、执行、记录、通知
4. 框架设计:提供扩展点的框架
Q10: 模板方法模式的优缺点是什么?
答案要点:
优点:
- 代码复用,避免重复代码
- 算法结构稳定,易于维护
- 扩展性好,支持子类扩展
- 符合开闭原则
缺点:
- 类数量增加,系统复杂度提高
- 继承关系,子类与父类耦合
- 模板方法修改影响所有子类
- 调试困难,流程分散在多个类中
5.6 源码分析类问题
Q11: Spring的AbstractApplicationContext.refresh()方法是如何设计的?
答案要点:
// 设计特点
1. 使用synchronized保证线程安全
2. 定义完整的容器刷新流程
3. 提供多个钩子方法供子类扩展
4. 统一的异常处理和资源管理
5. 支持不同应用上下文的定制
Q12: 如何测试模板方法模式?
答案要点:
// 测试策略
1. 测试抽象类:创建测试用的具体实现
2. 测试模板方法:验证执行流程
3. 测试钩子方法:测试不同条件分支
4. 使用Mock对象:验证方法调用
5. 集成测试:测试完整流程
5.7 设计模式对比类问题
Q13: 模板方法模式与其他模式的区别?
答案要点:
模式 | 关系 | 区别 |
---|---|---|
策略模式 | 组合 | 算法完全可替换 vs 算法结构固定 |
工厂方法 | 继承 | 创建对象 vs 定义算法 |
命令模式 | 组合 | 请求封装 vs 算法定义 |
责任链 | 组合 | 处理链 vs 算法步骤 |
Q14: 什么时候使用模板方法模式?
答案要点:
// 使用场景判断
if (多个类有相同算法结构) {if (算法结构稳定) {if (需要子类定制步骤) {使用模板方法模式;}}
}// 具体场景
1. 框架设计:提供扩展点
2. 业务流程:标准化流程
3. 数据处理:统一处理流程
4. 算法实现:算法结构相同
总结
模板方法模式是Spring框架中广泛使用的设计模式,它通过定义算法骨架和提供扩展点,实现了代码复用和灵活扩展的平衡。
关键要点
- 理解原理:掌握模板方法模式的核心思想和设计原则
- 掌握实现:学会设计模板方法和钩子方法
- Spring应用:理解Spring中模板方法模式的应用
- 实际应用:能够在项目中合理使用模板方法模式
- 测试方法:掌握模板方法模式的测试策略
通过深入学习模板方法模式,可以更好地理解Spring框架的设计思想,提升代码的可维护性和扩展性。