今天我们要讲的这个主角是在设计模式中是个大佬级的,它就是模板模式。相信之前是有小伙伴看过这个设计模式但是确不明白它为什么这么重要的,也相信本篇会让你有新的收获。
模版模式,很多博客里面是这么说它的:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,模板方法可以不改变一个算法的结构即可重定义该算法的某些特定步骤。在我们实际编程中,通常就用是一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。
我们先来看个很简单的例子,我们以生活中的做饭为例子,一般分为买东西,洗菜,最后就是烹饪这三个步骤,我们将这个过程用抽象类来表示一下就是下面的代码:
public abstract class Cook { protected abstract List<Food> buy(); protected void wash(List<Food> vegetables){ vegetables.forEach(Food::washed); } protected abstract List<CookedFood> cook(List<Food> vegetables); //模板方法 public final void cook(){ // 买东西,后代可以重写这个buy方法,也就是随便你想买啥回来都可以 List<Food> vegetables = buy(); // wash给了默认实现,也就是一般来说都是要洗菜的把,当然后代你可以把它重写了 wash(vegetables); // 最后一步就是烹饪过程,随便你想做成什么样的食物 List<CookedFood> cookedFoods = cook(vegetables); } }
然后假如我们想吃的是鱼,那我们可以这么写:
public class CookFish extends Cook{ @Override protected List<Food> buy() { List<Food> result = new ArrayList<>(); // 不好意思,我饭量大,需要来只鲨鱼 result.add(new Shark()); result.add(new Fish()); return result; } @Override protected void wash(List<Food> foods){ System.out.println("不洗了,直接捞来的吃着更有味道"); } @Override protected List<CookedFood> cook(List<Food> vegetables) { return vegetables.stream().map(CookedFood::new) .collect(Collectors.toList()); } }
其中Food是Fish和Shark的父类,就Food有一个washed的空方法,其他类都是空的,CookedFood里面有个成员属性是Food,这几个类代码过于简单,就不放出来占地方了。如果我们想做些别的饭比如龙虾什么的,那就再定义一个Cook的后代,然后自己再定义几个Food的子类,再把买、洗、烹饪这三个方法根据自己的需求重写即可。
是不是觉得很简单?好像花这么几分钟就能掌握的样子。对,就是这么简单😂,但是问题是你得玩的6才算真正的会啊,而不是仅仅是上面这一个花五分钟就能写出来的demo,这样子的话着实没什么卵用。我们一起来看看Spring中是怎么玩的吧。
我们正篇的第三篇中有提到过AbstractApplicationContext的refresh方法,很多人读spring的源码时候都是从这里开始的,这个方法其实也是个模板方法,我们来看看代码:
@Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // 1.加载前的准备工作 prepareRefresh(); // 2.获取一个全新的beanFactory实例 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // 3.初始化beanFactory,给它设置各种 prepareBeanFactory(beanFactory); try { // 4.允许对beanFactory中的东西做一些前置的修改,可以是增加个BeanFactoryPostProcessors这种的,也可以给增加个 // BeanDefinition,也可以增加一些让beanFactory在自动装配时候忽略掉的接口,也可以增加一些特定场景使用的bean, // 比如有的后代就增加了新的scope bean 等等。但是重点是,我刚才说的这些都是基于一种具体场景的,因此这个抽象类里, // 这个方法是空的(不弄成抽象的原因是不强迫子类去实现) postProcessBeanFactory(beanFactory); // 5.触发调用所有的BeanFactoryPostProcessors(后边会讲这是个啥) invokeBeanFactoryPostProcessors(beanFactory); // 6.注册所有的BeanPostProcessor(后边会说这个) registerBeanPostProcessors(beanFactory); // 7.初始化支持国际化的东东 initMessageSource(); // 8. 初始化事件广播器 initApplicationEventMulticaster(); // 9. 初始化其他特殊的bean onRefresh(); // 10. 注册监听器 registerListeners(); // 11. 实例化bean( BOSS难度代码 ) finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } catch (BeansException ex) { 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; } } }
这里面我只说第二步,因为我们前三篇讲的其实也都只是1和2的内容,我们来看看第二处的代码:
之所以放出所有的代码只是想让大家混个脸熟,别担心,放出来的我后边都会说😊
其实第二处的代码也就两行而已,我们重点看的是第一行的refreshBeanFactory这个方法
/** * Tell the subclass to refresh the internal bean factory. * @return the fresh BeanFactory instance * @see #refreshBeanFactory() * @see #getBeanFactory() */ protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { refreshBeanFactory(); ConfigurableListableBeanFactory beanFactory = getBeanFactory(); if (logger.isDebugEnabled()) { logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory); } return beanFactory; }
注意看了哈,这是个抽象方法了,注意看这里的注释,翻译过来意思就是子类必须实现这个这个方法来进行真实的配置加载,这个方法必须在任何其他的初始化工作之前被refresh触发。我们之前看的这个方法的实现是在我们使用xml配置这种情况下对应的AbstrctApplicationContext的子类中(也就是ClassPathXmlApplicationContext的抽象父类AbstractXmlApplicationContext),为了讲清楚东西,我再带着大家回顾一下之前的代码:
@Override protected final void refreshBeanFactory() throws BeansException { if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); customizeBeanFactory(beanFactory); // 加载BeanDefinition的 loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; } } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); } }
这是AbstractRefreshAbleApplicationContext中对上面refreBeanFactory方法的实现,而这里面标着注释的这句代码loadBeanDefinitons又是一个抽象方法,而这个抽象方法的有好几个实现,一个是我们之前提到过的AbstractXmlApplicationContext中的实现,它在这个方法中完成了对xml的加载解析,而另外一个实现就是AnnotationConfigWebApplicationContext中对它的实现,在这个实现里面,完成了对把我们标着那几个常见注解的类加载成了BeanDefiniton的这个过程。
然后再回归到AbstractApplicationContext中refresh方法中的refreshBeanFactory这个抽象方法来看,不难发现,不同的子类根据不同的场景实现了这个方法,进而根据不同的配置获取到了BeanDefinition,为接下来实例化bean做足了准备工作。其实在refresh方法中,还有别的方法也有类似的功能,就不在这里一一展开,否则这又是一篇很长的文章。
我们又想想这篇文章开头那个非常简单的demo上面,确实,那个小demo简单到任何人都能理解,但是我们仔细想一想,要用好这个模板模式的前提是你必须对你的业务流程非常的了解,一个大的流程中哪些是小的流程该抽象的,哪些需要有默认的实现,而哪些又是需要给空实现,这些都是在做模板方法(其实就是在抽象你的业务流程)的时候需要考虑到的问题(模板模式看重的就是这个流程),如果在设计之初就把这个设计做好的话,模板模式会给你带来无限多扩展的好处,但是如果没有用好,以至于用了一段时间后发现模板方法有问题,那么改起来会是非常痛苦的。
最后一句话也是画外音,我自己的使用感受是:这个模式非常好的把面向过程和面向对象结合在一起,抽象业务的时候是在玩面向过程,用子类去实现的时候是在玩面向对象,如果形容的不合适,你就当看看就好。