模板方法模式详解
目录
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 {
@Override
protected void doProcessData() {
System.out.println("处理XML数据");
}
}
public class JsonDataProcessor extends DataProcessor {
@Override
protected void doProcessData() {
System.out.println("处理JSON数据");
}
}
public class CsvDataProcessor extends DataProcessor {
@Override
protected 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 {
@Test
public void testTemplateMethod() {
// 创建测试用的具体实现
AbstractProcessor processor = new AbstractProcessor() {
@Override
protected void doProcess() {
System.out.println("测试处理");
}
@Override
protected boolean needAdditionalProcess() {
return true;
}
@Override
protected void additionalProcess() {
System.out.println("测试额外处理");
}
};
// 测试模板方法
processor.process();
// 验证执行结果
// 可以使用Mock对象验证方法调用
}
@Test
public void testHookMethods() {
// 测试钩子方法的不同情况
AbstractProcessor processor1 = new AbstractProcessor() {
@Override
protected void doProcess() {
// 实现
}
@Override
protected boolean needAdditionalProcess() {
return false; // 不执行额外处理
}
};
AbstractProcessor processor2 = new AbstractProcessor() {
@Override
protected void doProcess() {
// 实现
}
@Override
protected 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>() {
@Override
public 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();
// 创建PreparedStatement
ps = 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 {
// 刷新容器的模板方法
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 准备刷新
prepareRefresh();
// 获取BeanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 准备BeanFactory
prepareBeanFactory(beanFactory);
try {
// 允许BeanFactory的后处理
postProcessBeanFactory(beanFactory);
// 调用BeanFactoryPostProcessors
invokeBeanFactoryPostProcessors(beanFactory);
// 注册BeanPostProcessors
registerBeanPostProcessors(beanFactory);
// 初始化MessageSource
initMessageSource();
// 初始化ApplicationEventMulticaster
initApplicationEventMulticaster();
// 刷新特定上下文子类
onRefresh();
// 检查监听器Bean并注册
registerListeners();
// 实例化所有非懒加载的单例Bean
finishBeanFactoryInitialization(beanFactory);
// 完成刷新
finishRefresh();
} catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// 销毁已创建的Bean
destroyBeans();
// 重置'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的模板方法
@Override
public 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;
// 尝试从缓存中获取单例Bean
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
} else {
// 检查Bean定义是否存在
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// 检查父BeanFactory
BeanFactory 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 {
@Override
protected void onRefresh() throws BeansException {
// 特定上下文的刷新逻辑
super.onRefresh();
}
@Override
protected 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框架的设计思想,提升代码的可维护性和扩展性。
427

被折叠的 条评论
为什么被折叠?



