模板模式

1、豆浆制作问题

编写制作豆浆的程序,说明如下:

  • 1)制作豆浆的流程选材 ——> 添加配料 ——> 浸泡 ——> 放到豆浆机打碎
  • 2)通过添加不同的配料,可以制作出不同口味的豆浆
  • 3)选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的
  • 4)请使用模板方法模式完成

说明:因为模板方法模式比较简单,很容易就想到这个方案,因此就直接使用,不再使用传统的方案来引出模板方法模式

2、基本介绍

  • 1)模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern),在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行
  • 2)简单说,模板方法模式定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定步骤
  • 3)这种类型的设计模式属于行为型模式

原理类图

image.png
对原理类图的说明——即模板方法模式的角色和职责

  • AbstractClass抽象类中实现了模板方法,定义了算法的骨架,具体子类需要去实现其抽象方法或重写其中方法
  • ConcreteClass实现了抽象方法,已完成算法中特定子类的步骤

3、模板模式解决豆浆制作问题

UML 类图

image.png

核心代码

  1. /**
  2. * 抽象方法
  3. */
  4. public abstract class SoyaMilk {
  5. /**
  6. * 模板方法,定义为final禁止覆写
  7. */
  8. public final void make() {
  9. System.out.println(">>>>>>豆浆制作开始<<<<<<");
  10. useSoyBean();
  11. addIngredients();
  12. soak();
  13. mash();
  14. System.out.println(">>>>>>豆浆制作结束<<<<<<");
  15. }
  16. protected void useSoyBean() {
  17. System.out.println("Step1. 选用上好的黄豆.");
  18. }
  19. protected abstract void addIngredients();
  20. protected void soak() {
  21. System.out.println("Step3. 对黄豆和配料进行水洗浸泡.");
  22. }
  23. protected void mash() {
  24. System.out.println("Step4. 将充分浸泡过的黄豆和配料放入豆浆机中,开始打豆浆.");
  25. }
  26. }
  27. /**
  28. * 花生豆浆
  29. */
  30. public class PeanutSoyaMilk extends SoyaMilk {
  31. public PeanutSoyaMilk() {
  32. System.out.println("============花生豆浆============");
  33. }
  34. @Override
  35. protected void addIngredients() {
  36. System.out.println("Step2. 加入上好的花生.");
  37. }
  38. }
  39. /**
  40. * 红豆豆浆
  41. */
  42. public class RedBeanSoyaMilk extends SoyaMilk {
  43. public RedBeanSoyaMilk() {
  44. System.out.println("============红豆豆浆============");
  45. }
  46. @Override
  47. protected void addIngredients() {
  48. System.out.println("Step2. 加入上好的红豆.");
  49. }
  50. }
  51. /**
  52. * 芝麻豆浆
  53. */
  54. public class SesameSoyaMilk extends SoyaMilk {
  55. public SesameSoyaMilk() {
  56. System.out.println("============芝麻豆浆============");
  57. }
  58. @Override
  59. protected void addIngredients() {
  60. System.out.println("Step2. 加入上好的芝麻.");
  61. }
  62. }

调用模板方法

  1. SoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();
  2. peanutSoyaMilk.make();
  3. SoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();
  4. redBeanSoyaMilk.make();
  5. SoyaMilk sesameSoyaMilk = new SesameSoyaMilk();
  6. sesameSoyaMilk.make();
  7. /*
  8. ============花生豆浆============
  9. >>>>>>豆浆制作开始<<<<<<
  10. Step1. 选用上好的黄豆.
  11. Step2. 加入上好的花生.
  12. Step3. 对黄豆和配料进行水洗浸泡.
  13. Step4. 将充分浸泡过的黄豆和配料放入豆浆机中,开始打豆浆.
  14. >>>>>>豆浆制作结束<<<<<<
  15. ============红豆豆浆============
  16. >>>>>>豆浆制作开始<<<<<<
  17. Step1. 选用上好的黄豆.
  18. Step2. 加入上好的红豆.
  19. Step3. 对黄豆和配料进行水洗浸泡.
  20. Step4. 将充分浸泡过的黄豆和配料放入豆浆机中,开始打豆浆.
  21. >>>>>>豆浆制作结束<<<<<<
  22. ============芝麻豆浆============
  23. >>>>>>豆浆制作开始<<<<<<
  24. Step1. 选用上好的黄豆.
  25. Step2. 加入上好的芝麻.
  26. Step3. 对黄豆和配料进行水洗浸泡.
  27. Step4. 将充分浸泡过的黄豆和配料放入豆浆机中,开始打豆浆.
  28. >>>>>>豆浆制作结束<<<<<<
  29. */

4、钩子方法

  • 1)在模板方法模式的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为“钩子”
  • 2)还是用上面做豆浆的例子来讲解,比如,我们还希望制作纯豆浆,不添加任何的配料,请使用钩子方法对前面的模板方法进行改造

核心代码

  1. public abstract class SoyaMilk {
  2. public final void make() {
  3. // ...
  4. if (customAddIngredients()) {
  5. addIngredients();
  6. }
  7. // ...
  8. }
  9. // ...
  10. }
  11. /**
  12. * 纯豆浆
  13. */
  14. public class PureSoyaMilk extends SoyaMilk {
  15. public PureSoyaMilk() {
  16. System.out.println("============纯豆浆============");
  17. }
  18. @Override
  19. protected void addIngredients() {
  20. // 空实现即可
  21. }
  22. @Override
  23. protected Boolean customAddIngredients() {
  24. return false;
  25. }
  26. }

测试钩子方法

  1. SoyaMilk pureSoyaMilk = new PureSoyaMilk();
  2. pureSoyaMilk.make();
  3. /*
  4. ============纯豆浆============
  5. >>>>>>豆浆制作开始<<<<<<
  6. Step1. 选用上好的黄豆.
  7. Step3. 对黄豆和配料进行水洗浸泡.
  8. Step4. 将充分浸泡过的黄豆和配料放入豆浆机中,开始打豆浆.
  9. >>>>>>豆浆制作结束<<<<<<
  10. */

5、Spring 框架源码分析

AbstractApplicationContext.java中有一个refresh()方法就是模板方法,其中定义了抽象方法和钩子方法

  1. // 模板方法
  2. public void refresh() throws BeansException, IllegalStateException {
  3. synchronized (this.startupShutdownMonitor) {
  4. prepareRefresh();
  5. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
  6. prepareBeanFactory(beanFactory);
  7. try {
  8. postProcessBeanFactory(beanFactory); // 钩子方法
  9. invokeBeanFactoryPostProcessors(beanFactory);
  10. registerBeanPostProcessors(beanFactory);
  11. initMessageSource();
  12. initApplicationEventMulticaster();
  13. onRefresh(); // 钩子方法
  14. registerListeners();
  15. finishBeanFactoryInitialization(beanFactory);
  16. finishRefresh();
  17. }
  18. catch (BeansException ex) {
  19. if (logger.isWarnEnabled()) {
  20. logger.warn("Exception encountered during context initialization - " +
  21. "cancelling refresh attempt: " + ex);
  22. }
  23. destroyBeans();
  24. cancelRefresh(ex);
  25. throw ex;
  26. }
  27. finally {
  28. resetCommonCaches();
  29. }
  30. }
  31. }
  32. protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
  33. refreshBeanFactory(); // 抽象方法
  34. ConfigurableListableBeanFactory beanFactory = getBeanFactory(); // 抽象方法
  35. if (logger.isDebugEnabled()) {
  36. logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
  37. }
  38. return beanFactory;
  39. }
  40. protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
  41. }
  42. protected void onRefresh() throws BeansException {
  43. // For subclasses: do nothing by default.
  44. }

UML 类图

image.png

6、注意事项和细节

  • 1)基本思想:算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改
  • 2)实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用
  • 3)既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现
  • 4)不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大
  • 5)一般模板方法都加上final关键字,防止子类重写模板方法
  • 6)使用场景:当要完成在某个过程,该过程要执行一系列步骤,这一系列的步骤基本相同,但其个别步骤在实现时可能不同,通常考虑用模板方法模式来处理