行为型设计模式 - 模板方法模式详解及其在Spring中的应用

基本介绍

模板方法模式(Template Method Pattern)也叫模板模式,它在一个抽象类中公开定义了执行它的方法的模板,它的字类可以按需重写方法实现,但调用将以抽象类中定义的方式进行。

简单来说,模板方法模式定义一个操作中的算法的骨架,将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就能重新定义该算法的某些特定步骤

模式结构

  • AbstractClass:抽象类,类中实现了模板方法,定义了算法的骨架

  • ConcreteClass:实现类,继承抽象类,重写其中的抽象方法

举例说明

编写一个制作豆浆的程序

制作豆浆的流程:选材(黄豆) → 添加配料 → 浸泡 → 磨豆浆

要求:通过添加不同的配料,可以制作出不同口味的豆浆(除了添加配料不同,其它步骤是相同的)

1、定义一个抽象类,BaseSoyMilk:豆浆类

public abstract class BaseSoyMilk {
    /**
     * 模板方法,制作豆浆的流程
     * 声明为 final,防止子类重写
     */
    protected final void make() {
        select();
        addOther();
        soak();
        grind();
    }

    private void select() {
        System.out.println("选择上好的黄豆");
    }

    protected abstract void addOther();

    private void soak() {
        System.out.println("浸泡原材料");
    }

    private void grind() {
        System.out.println("研磨成豆浆");
    }
}

2、创建子类,PeanutSoyMilk:花生豆浆

public class PeanutSoyMilk extends BaseSoyMilk {
    @Override
    protected void addOther() {
        System.out.println("添加花生");
    }
}

RedBeanSoyMilk:红豆豆浆

public class RedBeanSoyMilk extends BaseSoyMilk {
    @Override
    protected void addOther() {
        System.out.println("添加红豆");
    }
}

3、创建测试类:Client

public class Client {
    @Test
    public void test() {
        BaseSoyMilk peanutSoyMilk = new PeanutSoyMilk();
        peanutSoyMilk.make();
        System.out.println("****************");
        BaseSoyMilk redBeanSoyMilk = new RedBeanSoyMilk();
        redBeanSoyMilk.make();
    }
}

4、运行结果

选择上好的黄豆
添加花生
浸泡原材料
研磨成豆浆
************
选择上好的黄豆
添加红豆
浸泡原材料
研磨成豆浆

钩子方法

在模板方法模式的抽象类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况来决定是否重写它,该方法称为「钩子」。

依然以上面制作豆浆的例子来说明,如果我们什么配料都不想加,制作纯豆浆,可以使用钩子方法对其改造。

1、为 BaseSoyMilk 类添加一个方法 isAdd 判断是否添加其它材料

public abstract class BaseSoyMilk {
    /**
     * 模板方法,制作豆浆的流程
     * 声明为 final,防止子类重写
     */
    protected final void make() {
        select();
        if(isAdd()){
            addOther();
        }
        soak();
        grind();
    }

    private void select() {
        System.out.println("选择上好的黄豆");
    }

    protected abstract void addOther();

    private void soak() {
        System.out.println("浸泡原材料");
    }

    private void grind() {
        System.out.println("研磨成豆浆");
    }

    protected boolean isAdd(){
        return true;
    }
}

2、创建一个子类 PureSoyMilk:纯豆浆

public class PureSoyMilk extends BaseSoyMilk {
    @Override
    protected void addOther() {
    }

    @Override
    protected boolean isAdd() {
        return false;
    }
}

3、测试

@Test
public void test2() {
    BaseSoyMilk pureSoyMilk = new PureSoyMilk();
    pureSoyMilk.make();
}

4、运行结果(现在就没有添加其它材料的步骤了)

选择上好的黄豆
浸泡原材料
研磨成豆浆

模式分析

🎉 优点

  • 算法只存在于父类中,易修改
  • 实现了代码的复用性,父类的一些方法直接被子类使用
  • 既统一了算法,也保证了很大的灵活性

💣 缺点

  • 每一个不同的实现都需要一个子类来实现,导致类的数量增加,是系统更庞大

🎯 注意事项

  • 一般模板方法要加上 final 修饰符,防止子类重写

🎲 使用场景

  • 当我们要完成某个过程,该过程有一系列步骤,而且这些步骤基本相同,只有个别步骤的实现可能不同,可以考虑使用模板方法模式

在 Spring 源码中的应用

模板方法模式在 Spring IOC 容器初始化的时候有所应用,我们看一下 AbstractApplicationContext 中的 refresh 方法,它就是一个模板方法,里面调用了一系列方法,有以实现的具体方法,有未实现的抽象方法,也有空的钩子方法

public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      prepareRefresh();
       //此方法内部调用了两个抽象方法refreshBeanFactory()和getBeanFactory()
       //具体要取哪种beanFactory的控制权交给了子类
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
      prepareBeanFactory(beanFactory);
      try {
          //钩子方法
         postProcessBeanFactory(beanFactory);
         invokeBeanFactoryPostProcessors(beanFactory);
         registerBeanPostProcessors(beanFactory);
         initMessageSource();
         initApplicationEventMulticaster();
          //钩子方法
         onRefresh();
         registerListeners();
         finishBeanFactoryInitialization(beanFactory);
         finishRefresh();
      }
      catch (BeansException ex) {
         if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                  "cancelling refresh attempt: " + ex);
         }
         destroyBeans();
         cancelRefresh(ex);
         throw ex;
      }
      finally {
         resetCommonCaches();
      }
   }
}

部分结构类图如下

posted @ 2020-04-23 17:05  农夫三拳有点疼~  阅读(889)  评论(2编辑  收藏  举报