一、模板模式简介
1.1 简介
定义一个操作中算法的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变算法的结构即可重定义该算法的某些特定步骤。
通俗点的理解就是 :完成一件事情,有固定的数个步骤,但是每个步骤根据对象的不同,而实现细节不同;就可以在父类中定义一个完成该事情的总方法,按照完成事件需要的步骤去调用其每个步骤的实现方法。每个步骤的具体实现,由子类完成。
抽象父类(AbstractClass):实现了模板方法,定义了算法的骨架。
具体类(ConcreteClass):实现抽象类中的抽象方法,即不同的对象的具体实现细节。
1.2 使用场景
使用用场景:
a) 在多个子类中拥有相同的方法,而且逻辑相同时,可以将这些方法抽出来放到一个模板抽象类中。
b) 程序主框架相同,细节不同的情况下,也可以使用模板方法。如JDBC查询数据库,文件操作,数据导出操作等。
- 融山系统使用场景:
a) 数据门户数据填报时,三大业务线在修改数据时逻辑基本相同,数据完整性校验 -> 身份校验 -> 数据权限校验 -> CURD -> 刷新缓存
b) 从token 获取参数时,流程基本相同, token 正确性检查 -> 获取token参数
二、模板模式如何实现
2.1 定义抽象模板类:TokenTemplete
@Slf4j
public abstract class TokenTemplete {
abstract String creatToken(BaseToken token);
abstract Boolean checkToken(String token);
abstract Map getParms(String token);
//具体的整个过程
public final Map checkAndGetParms(String token){
Boolean checkResult = checkToken(token);
if(checkResult){
return getParms(token);
}else {
log.error("token 校验失败");
return null;
}
}
}
2.2 下来做两个token 校验(checkToken) 和获取token参数(getParms)的方法 实现父类中的抽象方法
- TempleteOperationAdminUserToken
@Slf4j
@Component
public class TempleteOperationAdminUserToken extends TokenTemplete {
@Override
public String creatToken(BaseToken token) {
log.info("adminUserToken生成成功");
return "adminUserToken 生成成功";
}
@Override
public Boolean checkToken(String token) {
log.info("token ={}, adminUserToken 检验成功", token);
return true;
}
@Override
public Map getParms(String token) {
log.info("adminUserToken 获取参数");
HashMap hashMap = new HashMap<>();
hashMap.put("tokenType", "adminUserToken");
return hashMap;
}
}
- TempleteOperationApplicationToken
@Slf4j
@Component
public class TempleteOperationApplicationToken extends TokenTemplete {
@Override
public String creatToken(BaseToken token) {
log.info("applicationToken生成成功");
return "applicationToken 生成成功";
}
@Override
public Boolean checkToken(String token) {
log.info("token ={}, applicationToken 校验成功", token);
return true;
}
@Override
public Map getParms(String token) {
log.info("applicationToken 参数获取成功");
HashMap hashMap = new HashMap<>();
hashMap.put("tokenType", "application token");
return hashMap;
}
}
2.3 正式使用
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class GofTempleteTests {
@Test
public void test01(){
String token01 = "应用所携带的token";
TokenTemplete tokenTemplete = new TempleteOperationApplicationToken();
tokenTemplete.checkAndGetParms(token01);
String token02 = "用户登陆时所携带的token";
tokenTemplete = new TempleteOperationAdminUserToken();
tokenTemplete.checkAndGetParms(token02);
}
}
三、模板方式的优点和不足
3.1 优点
a) 具体细节步骤实现定义在子类中,子类定义详细处理算法是不会改变算法整体结构。
b)代码复用的基本技术,在数据库设计中尤为重要。
c)存在一种反向的控制结构,通过一个父类调用其子类的操作,通过子类对父类进行扩展增加新的行为,符合“开闭原则”。
3.2 不足
a) 每个不同的实现都需要定义一个子类,会导致类的个数增加,系统更加庞大。
b) 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
四、其它注意事项
4.1 抽象类中装饰符的使用
- final修饰,保证其不会被子类修改,而模板方法则使用protected修饰,表明其需要在子类中实现。
五、Spring 中的应用
Spring中几乎所有的扩展,都使用了模板方法模式, 如JdbcTemplate 和 Spring IOC容器初始化
5.1 Spring IOC容器初始化
a) 首先定义一个接口ConfigurableApplicationContext,声明模板方法refresh
public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {
/**声明了一个模板方法*/
void refresh() throws BeansException, IllegalStateException;
}
b) 抽象类AbstractApplicationContext实现了接口,主要实现了模板方法refresh(这个方法很重要,是各种IOC容器初始化的入口)的逻辑
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext, DisposableBean {
/**模板方法的具体实现*/
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
//注意这个方法是,里面调用了两个抽象方法refreshBeanFactory、getBeanFactory
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
//注意这个方法是钩子方法
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
//注意这个方法是钩子方法
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
}
}
c) 这里最主要有一个抽象方法obtainFreshBeanFactory、两个钩子方法postProcessBeanFactory和onRefresh,看看他们在类中的定义
两个钩子方法:
/**
* Modify the application context's internal bean factory after its standard
* initialization. All bean definitions will have been loaded, but no beans
* will have been instantiated yet. This allows for registering special
* BeanPostProcessors etc in certain ApplicationContext implementations.
* @param beanFactory the bean factory used by the application context
*/
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
}
/**
* Template method which can be overridden to add context-specific refresh work.
* Called on initialization of special beans, before instantiation of singletons.
* This implementation is empty.
* @throws BeansException in case of errors
* @see #refresh()
*/
protected void onRefresh() throws BeansException {
// For subclasses: do nothing by default.
}
再看看获取Spring容器的抽象方法:
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
具体要取那种BeanFactory容器的决定权交给了子类!
d)、具体实现的子类,实现了抽象方法getBeanFactory的子类有:
AbstractRefreshableApplicationContext:
public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {
@Override
public final ConfigurableListableBeanFactory getBeanFactory() {
synchronized (this.beanFactoryMonitor) {
if (this.beanFactory == null) {
throw new IllegalStateException("BeanFactory not initialized or already closed - " +
"call 'refresh' before accessing beans via the ApplicationContext");
}
//这里的this.beanFactory在另一个抽象方法refreshBeanFactory的设置的
return this.beanFactory;
}
}
}
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
@Override
public final ConfigurableListableBeanFactory getBeanFactory() {
//同样这里的this.beanFactory在另一个抽象方法中设置
return this.beanFactory;
}
}