1. 程式人生 > 其它 >【設計模式從入門到精通】12-模板模式

【設計模式從入門到精通】12-模板模式

模板模式

筆記來源:尚矽谷Java設計模式(圖解+框架原始碼剖析)

目錄

模板模式

1、豆漿製作問題

編寫製作豆漿的程式,說明如下:

  • 1)製作豆漿的流程選材 ----> 新增配料 ----> 浸泡 ----> 放到豆漿機打碎
  • 2)通過新增不同的配料,可以製作出不同口味的豆漿
  • 3)選材、浸泡和放到豆漿機打碎這幾個步驟對於製作每種口味的豆漿都是一樣的
  • 4)請使用模板方法模式完成

說明:因為模板方法模式比較簡單,很容易就想到這個方案,因此就直接使用,不再使用傳統的方案來引出模板方法模式

2、基本介紹

  • 1)模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern),在一個抽象類公開定義了執行它的方法的模板。它的子類可以按需要重寫方法實現,但呼叫將以抽象類中定義的方式進行
  • 2)簡單說,模板方法模式定義一個操作中的演算法的骨架,而將一些步驟延遲到子類中,使得子類可以不改變一個演算法的結構,就可以重定義該演算法的某些特定步驟
  • 3)這種型別的設計模式屬於行為型模式

原理類圖

對原理類圖的說明——即模板方法模式的角色和職責

  • AbstractClass抽象類中實現了模板方法,定義了演算法的骨架,具體子類需要去實現其抽象方法或重寫其中方法
  • ConcreteClass實現了抽象方法,已完成演算法中特定子類的步驟

3、模板模式解決豆漿製作問題

UML 類圖

核心程式碼

/**
 * 抽象方法
 */
public abstract class SoyaMilk {
    /**
     * 模板方法,定義為final禁止覆寫
     */
    public final void make() {
        System.out.println(">>>>>>豆漿製作開始<<<<<<");
        useSoyBean();
        addIngredients();
        soak();
        mash();
        System.out.println(">>>>>>豆漿製作結束<<<<<<");
    }

    protected void useSoyBean() {
        System.out.println("Step1. 選用上好的黃豆.");
    }

    protected abstract void addIngredients();

    protected void soak() {
        System.out.println("Step3. 對黃豆和配料進行水洗浸泡.");
    }

    protected void mash() {
        System.out.println("Step4. 將充分浸泡過的黃豆和配料放入豆漿機中,開始打豆漿.");
    }
}
/**
 * 花生豆漿
 */
public class PeanutSoyaMilk extends SoyaMilk {
    public PeanutSoyaMilk() {
        System.out.println("============花生豆漿============");
    }
    @Override
    protected void addIngredients() {
        System.out.println("Step2. 加入上好的花生.");
    }
}
/**
 * 紅豆豆漿
 */
public class RedBeanSoyaMilk extends SoyaMilk {
    public RedBeanSoyaMilk() {
        System.out.println("============紅豆豆漿============");
    }
    @Override
    protected void addIngredients() {
        System.out.println("Step2. 加入上好的紅豆.");
    }
}
/**
 * 芝麻豆漿
 */
public class SesameSoyaMilk extends SoyaMilk {
        public SesameSoyaMilk() {
        System.out.println("============芝麻豆漿============");
    }
    @Override
    protected void addIngredients() {
        System.out.println("Step2. 加入上好的芝麻.");
    }
}

呼叫模板方法

SoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();
peanutSoyaMilk.make();
SoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();
redBeanSoyaMilk.make();
SoyaMilk sesameSoyaMilk = new SesameSoyaMilk();
sesameSoyaMilk.make();
/*
============花生豆漿============
>>>>>>豆漿製作開始<<<<<<
Step1. 選用上好的黃豆.
Step2. 加入上好的花生.
Step3. 對黃豆和配料進行水洗浸泡.
Step4. 將充分浸泡過的黃豆和配料放入豆漿機中,開始打豆漿.
>>>>>>豆漿製作結束<<<<<<
============紅豆豆漿============
>>>>>>豆漿製作開始<<<<<<
Step1. 選用上好的黃豆.
Step2. 加入上好的紅豆.
Step3. 對黃豆和配料進行水洗浸泡.
Step4. 將充分浸泡過的黃豆和配料放入豆漿機中,開始打豆漿.
>>>>>>豆漿製作結束<<<<<<
============芝麻豆漿============
>>>>>>豆漿製作開始<<<<<<
Step1. 選用上好的黃豆.
Step2. 加入上好的芝麻.
Step3. 對黃豆和配料進行水洗浸泡.
Step4. 將充分浸泡過的黃豆和配料放入豆漿機中,開始打豆漿.
>>>>>>豆漿製作結束<<<<<<
*/

4、鉤子方法

  • 1)在模板方法模式的父類中,我們可以定義一個方法,它預設不做任何事,子類可以視情況要不要覆蓋它,該方法稱為“鉤子”
  • 2)還是用上面做豆漿的例子來講解,比如,我們還希望製作純豆漿,不新增任何的配料,請使用鉤子方法對前面的模板方法進行改造

核心程式碼

public abstract class SoyaMilk {
    public final void make() {
        // ...
        if (customAddIngredients()) {
            addIngredients();
        }
        // ...
    }
    // ...
}

/**
 * 純豆漿
 */
public class PureSoyaMilk extends SoyaMilk {
    public PureSoyaMilk() {
        System.out.println("============純豆漿============");
    }

    @Override
    protected void addIngredients() {
        // 空實現即可
    }

    @Override
    protected Boolean customAddIngredients() {
        return false;
    }
}

測試鉤子方法

SoyaMilk pureSoyaMilk = new PureSoyaMilk();
pureSoyaMilk.make();
/*
============純豆漿============
>>>>>>豆漿製作開始<<<<<<
Step1. 選用上好的黃豆.
Step3. 對黃豆和配料進行水洗浸泡.
Step4. 將充分浸泡過的黃豆和配料放入豆漿機中,開始打豆漿.
>>>>>>豆漿製作結束<<<<<<
*/

5、Spring 框架原始碼分析

AbstractApplicationContext.java中有一個refresh()方法就是模板方法,其中定義了抽象方法和鉤子方法

// 模板方法
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        prepareRefresh();
        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();
        }
    }
}
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
    refreshBeanFactory(); // 抽象方法
    ConfigurableListableBeanFactory beanFactory = getBeanFactory(); // 抽象方法
    if (logger.isDebugEnabled()) {
        logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
    }
    return beanFactory;
}
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
}
protected void onRefresh() throws BeansException {
    // For subclasses: do nothing by default.
}

UML 類圖

6、注意事項和細節

  • 1)基本思想:演算法只存在於一個地方,也就是在父類中,容易修改。需要修改演算法時,只要修改父類的模板方法或者已經實現的某些步驟,子類就會繼承這些修改
  • 2)實現了最大化程式碼複用。父類的模板方法和已實現的某些步驟會被子類繼承而直接使用
  • 3)既統一了演算法,也提供了很大的靈活性。父類的模板方法確保了演算法的結構保持不變,同時由子類提供部分步驟的實現
  • 4)不足之處:每一個不同的實現都需要一個子類實現,導致類的個數增加,使得系統更加龐大
  • 5)一般模板方法都加上final關鍵字,防止子類重寫模板方法
  • 6)使用場景:當要完成在某個過程,該過程要執行一系列步驟,這一系列的步驟基本相同,但其個別步驟在實現時可能不同,通常考慮用模板方法模式來處理