“项目评审的节点又快到了,PPT你写了没?”
“Oops,忘了,有模板没?给我一份”
概述
模板,一个频繁出现在办公室各类角色口中的词,它通常意味着统一、高效、经验和优质。各项汇报因为PPT的模板变得更加生动,各式的报告因为有了文档模板变得更加规范。找规律是人类很愿意钻研的工作之一,那么在设计模式中,是否有一种模板能够帮我们解决一些问题呢?
答案是肯定的,有!
一言
模板方法模式定义一个操作中的算法的骨架,将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重新定义该算法的某些特定步骤。
炼金术
为了更好的理解模板模式,我想起了这样一个场景。我们都玩过打怪升级的养成类游戏,拿到了野外的原料再去城里的铁匠铺锻造,不同的材料会合成不同的装备。
如果让你用代码实现这个需求,你有什么样的思路?
我们只要稍加思索就会发现,这个需求中大部分的过程都是不变的,唯一的变数就在于投入的原料,在这样的场景下,模板方法模式就非常契合我们的需求了。
原理
- AbstractClass抽象类,类中实现了模板方法,定义了算法骨架,具体子类需要实现其它抽象方法operation2,3,4;
- ConcreteClass实现抽象方法operation2,3,4,以完成算法中特定子类的步骤;
简单实现
铁匠铺
public abstract class BlackSmith {final void make(){fire();putMaterial();hit();}void fire(){System.out.println("锻造炉升温");}abstract void putMaterial();void hit(){System.out.println("开始制作");}
}
铸剑室
public class SwordBlackSmith extends BlackSmith{@Overridevoid putMaterial() {System.out.println("放入铸剑材料:铁矿石");}
}
制弓室
public class ArchBlackSmith extends BlackSmith{@Overridevoid putMaterial() {System.out.println("放入制弓材料:木材,牛筋");}
}
钩子方法
上面的实现通俗易懂,下面我们思考一下,模板方法中,如果我需要视情况规避掉其中几个方法的执行,应该如何实现?
比如说:”冬天到了,铁匠铺没有生意,但是铁匠很冷,他只想让锻造炉升温,但是不需要放入任何锻造材料“。你有什么思路?
实际上这种需求就可以用钩子方法来实现,下面我们在原来的实现上稍加处理。
铁匠铺(含钩子)
public abstract class BlackSmith {final void make(){fire();if (work()){putMaterial();hit();}}void fire(){System.out.println("锻造炉升温");}abstract void putMaterial();void hit(){System.out.println("开始制作");}boolean work(){return true;}
}
凛冬将至
public class WinterIsComing extends BlackSmith{@Overridevoid putMaterial() {}@Overrideboolean work() {return false;}
}
IOC源码中模板方法模式的应用
相信刷过Java八股的同学在刚刚读到钩子方法的时候就已经想到了IOC容器初始化的实现了,各种论坛、文档上只要一提IOC过程、容器初始化 必然会说到钩子方法,但是相信很多同学也和笔者起初一样,对这种生涩的描述感觉一头雾水。今天我就带大家把这个听起来很晦涩的描述讲通。
没错,作为设计模式的集大成者,springframework在IOC的源码实现中包含着大量的钩子函数实现,这里也是对模板方法模式的主要应用。
我们先来看框架结构:
spring源码中,上下文接口ApplicationContext是很靠近底层的一个接口,它也是很重要的一个衔接接口。ConfigurableApplicationContext接口作为ApplicaitonContext的一个分支实现,承担了大量的springIOC初始化工作,而ConfigurableApplicationContext有一个核心的实现类AbstractApplicationContext,在这个实现类中有一个很重要很重要很重要的方法,就是refresh()方法。
我愿意称这个方法为spring之梦开始的地方,这个方法就是模板方法模式的典型应用,包含了各种前置后置实现和钩子方法,更是spring生命周期的核心体现。
相关源码
@Overridepublic void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");// Prepare this context for refreshing.prepareRefresh();// 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);StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");// Invoke factory processors registered as beans in the context.invokeBeanFactoryPostProcessors(beanFactory);// Register bean processors that intercept bean creation.registerBeanPostProcessors(beanFactory);beanPostProcess.end();// 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) {if (logger.isWarnEnabled()) {logger.warn("Exception encountered during context initialization - " +"cancelling refresh attempt: " + ex);}// Destroy already created singletons to avoid dangling resources.destroyBeans();// Reset 'active' flag.cancelRefresh(ex);// Propagate exception to caller.throw ex;}finally {// Reset common introspection caches in Spring's core, since we// might not ever need metadata for singleton beans anymore...resetCommonCaches();contextRefresh.end();}}}
在这个方法中,postProcessBeanFactory,onRefresh都是预留的钩子方法,在这里都是空实现。而对于这些钩子的实现往往依赖于更高层的子类,比如说:GenericWebApplicationContext。
相关源码
/*** 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.* <p>This implementation is empty.* @throws BeansException in case of errors* @see #refresh()*/protected void onRefresh() throws BeansException {// For subclasses: do nothing by default.}
/*** Register request/session scopes, environment beans, a {@link ServletContextAwareProcessor}, etc.*/@Overrideprotected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {if (this.servletContext != null) {beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext));beanFactory.ignoreDependencyInterface(ServletContextAware.class);}WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext);WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.servletContext);}//.../*** Initialize the theme capability.*/@Overrideprotected void onRefresh() {this.themeSource = UiApplicationContextUtils.initThemeSource(this);}
GenericWebApplicationContext对两个钩子写了具体的实现并根据自己的需求在refresh时按部就班的触发,形成了类似"子——父——子"的调用关系,这就是模板方法模式的魅力所在。
怎么样,相信大家看到这里会发现其实源码并没有那么晦涩,一些听到就打怵的词汇也没有那么高深。感兴趣的同学可以根据我的描述自己再追溯一下上述源码深度理解下。
结
模板方法模式的基本思想其实还是想让算法只存在于一处,这样更方便修改。本质上还是为了实现最大化的代码复用。就像我们开始说的PPT模板一样,父类模板方法实现的某些步骤可以直接被子类拿来使用。
这样既统一了算法,也提供了很大的灵活性。父类模板结构稳定,子类实现花样百出。
不过这种设计模式也存在着短板,就是可能引发类爆炸的问题,每一个不同的实现都需要一个子类,系统会逐渐变得笨重。所以一般模板方法都会加上final,防止子类重写模板方法。
关注我,共同进步,每天进步一点点。——Wayne