行為型設計模式 - 模板方法模式詳解及其在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();
      }
   }
}

部分結構類圖如下


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM