一分鐘學會《模板方法模式》

Java3y發表於2019-01-19

前言

只有光頭才能變強。

文字已收錄至我的GitHub倉庫,歡迎Star:https://github.com/ZhongFuCheng3y/3y

在上一篇有讀者說,一分鐘就看完門面模式了,所以今天的標題就取《一分鐘學會模板方法模式》

回顧前面所寫過的設計模式:

無論是面試還是個人的提升,設計模式是必學的。今天來講解模板方法模式~

一、模板方法模式

1.1模板方法模式現例項子

大家都知道,我每次寫原創技術文章,開頭總會有“只有光頭才能變強”。我當然不可能每次寫文章的時候都去複製這句話(因為這樣太麻煩了)。

我有自己的寫作模板,給大家看一下:

我的寫作模板

前言和最後都是固定下來的,至於第一點和第二點就得看是寫什麼文章,寫不同的文章對應的內容也是不一樣的。

每次我寫文章的時候,只要在這個模板上新增我自己想寫的東西就好了,就不用每次都複製一遍相同的內容,這樣就大大減少我的工作量啦。

1.2回到程式碼世界

程式碼來源於生活,同樣地我可以將我寫文章的過程用程式碼來描述,大家來看一下。

3y每篇文章都會有“前言”和“最後”的內容,3y把這兩個模組寫出來了。


// 3y的文章模板
public class Java3yWriteArticle {
    
    // 前言
    public void introduction() {
        System.out.println("只有光頭才能變強");
    }

    // 最後
    public void theLast() {
        System.out.println("關注我的公眾號:Java3y");

    }
}

3y寫文章的時候,3y可能就會這樣使用:


    // 3y寫文章
    public static void main(String[] args) {

        Java3yWriteArticle writeArticle = new Java3yWriteArticle();

        // 前言
        writeArticle.introduction();

        // 實際內容
        System.out.println("大家好,我是3y,今天來給大家分享我寫的模板方法模式");

        // 最後
        writeArticle.theLast();
    }

這樣是可以完成3y寫文章的功能,但是這樣做好嗎?這時候3y女朋友也想寫文章,她的文章同樣也想有“前言”和“最後”兩個模組,所以3y女朋友的文章模板是這樣的:


// 3y女朋友的文章模板
public  class Java3yGFWriteArticle {

    // 前言
    public void introduction() {
        System.out.println("balabalabalalabalablablalab");
    }

    // 最後
    public void theLast() {
        System.out.println("balabalabalalabalablablalab");

    }
}

那3y女朋友寫文章的時候,可能也會這樣使用:


    // 3y女朋友寫文章
    public static void main(String[] args) {
        Java3yGFWriteArticle java3yGFWriteArticle = new Java3yGFWriteArticle();

        // 前言
        java3yGFWriteArticle.introduction();

        // 實際內容
        System.out.println("3y是傻子,不用管他");

        // 最後
        java3yGFWriteArticle.theLast();

    }

可以發現3y和3y女朋友要寫文章的時是要重複呼叫introduction();theLast();。並且,3y的文章模板和3y女朋友的文章模板中的“前言”和“最後”只是實現內容的不同,卻定義了兩次,明顯就是重複的程式碼。面對重複的程式碼我們會做什麼?很簡單,抽取出來!

於是我們就可以抽取出一個通用的WriteArticle(為了方便呼叫,我們還將寫文章的步驟封裝成一個方法):


// 通用模板
public abstract class WriteArticle {

    // 每個人的“前言”都不一樣,所以抽象(abstract)
    protected abstract void introduction();

    // 每個人的“最後”都不一樣,所以抽象(abstract)
    protected abstract void theLast();


    // 實際要寫的內容,每個人的“實際內容”都不一樣,所以抽象(abstract)
    protected abstract void actualContent();
    
    // 寫一篇完整的文章(為了方便呼叫,我們將這幾個步驟分裝成一個方法)
    public final void writeAnCompleteArticle() {

        // 前言
        introduction();

        // 實際內容
        actualContent();

        // 最後
        theLast();
    }
}

所以,3y的模板就可以繼承通用模板,在通用模板上實現自己想要的就好了:


// 3y的文章模板
public  class Java3yWriteArticle extends WriteArticle {

    // 前言
    @Override
    public void introduction() {
        System.out.println("只有光頭才能變強");
    }

    // 最後
    @Override
    public void theLast() {
        System.out.println("關注我的公眾號:Java3y");

    }
    @Override
    protected void actualContent() {
        System.out.println("大家好,我是3y,今天來給大家分享我寫的模板方法模式");
    }
}

同樣地,3y女朋友的文章模板也是類似的:


// 3y女朋友的文章模板
public  class Java3yGFWriteArticle extends WriteArticle {

    // 前言
    @Override
    public void introduction() {
        System.out.println("balabalabalalabalablablalab");
    }

    // 最後
    @Override
    public void theLast() {
        System.out.println("balabalabalalabalablablalab");

    }

    @Override
    protected void actualContent() {
        System.out.println("3y是傻子,不用管他");
    }
}

想要真正寫文章的時候就十分方便了:


    // 3y寫文章
    public static void main(String[] args) {

        WriteArticle java3ywriteArticle = new Java3yWriteArticle();
        java3ywriteArticle.writeAnCompleteArticle();
    }

    // 3y女朋友寫文章
    public static void main(String[] args) {
        WriteArticle java3yGFWriteArticle = new Java3yGFWriteArticle();
        java3yGFWriteArticle.writeAnCompleteArticle();
    }

要點:

  • 把公共的程式碼抽取出來,如果該功能是不確定的,那我們將其修飾成抽象方法。
  • 將幾個固定步驟的功能封裝到一個方法中,對外暴露這個方法,就可以非常方便呼叫了。

嗯,上面的就是模板方法模式,就這麼簡單!

1.3模板方法模式介紹

《設計模式之禪》:

定義一個操作中的演算法框架,而將一些步驟延遲到子類中。使子類可以不改變一個演算法的結構即可重定義該演算法的某些步驟。

根據我們上面的例子,來講講這段話的含義:

  • 定義一個操作中的演算法框架,而將一些步驟延遲到子類中。

    • WriteArticle中有一個writeAnCompleteArticle()方法,該方法定義了發文章的所有步驟,但是這些步驟大多是抽象的,得由子類來實現。
  • 使子類可以不改變一個演算法的結構即可重定義該演算法的某些步驟

    • 外界是通過呼叫writeAnCompleteArticle()方法來寫文章的,子類如果改變具體的實現就會間接改變了演算法的細節。

比如3y在文章模板中的introduction()改了,“只有充錢才能變強”


    @Override
    public void introduction() {
        System.out.println("只有充錢才能變強");
    }

我們沒有碰過writeAnCompleteArticle()的程式碼,但再次呼叫這個方法的時候,具體的實現就會發生改變(因為writeAnCompleteArticle受子類的具體實現影響)

下面我們看一下模板方法模式的通用類圖:

模板方法模式的通用類圖

在模板方法模式中,也有幾個術語,根據我們的例子中的註釋,我給大家介紹一下:


// 抽象模板類
public abstract class WriteArticle {


    // 基本方法
    protected abstract void introduction();

    // 基本方法
    protected abstract void theLast();
    
    // 基本方法
    protected abstract void actualContent();

    // 模板方法
    public final void writeAnCompleteArticle() {
        introduction();
        actualContent();
        theLast();
    }
}

// 具體模板類
public class Java3yWriteArticle extends WriteArticle {

    // 實現基本方法
    @Override
    public void introduction() {
        System.out.println("只有充錢才能變強");
    }

    // 實現基本方法
    @Override
    public void theLast() {
        System.out.println("關注我的公眾號:Java3y");
    }

    // 實現基本方法
    @Override
    protected void actualContent() {
        System.out.println("大家好,我是3y,今天來給大家分享我寫的模板方法模式");
    }
}
  • 基本方法:在子類實現,並且在模板方法中被呼叫
  • 模板方法:定義了一個框架,實現對基本方法的呼叫,完成固定的邏輯

1.4模板方法的優缺點

優點:

  • 封裝不變的部分,擴充套件可變的部分。把認為是不變的部分的演算法封裝到父類,可變部分的交由子類來實現!
  • 提取公共部分的程式碼,行為由父類控制,子類實現!

缺點:

  • 抽象類定義了部分抽象方法,這些抽象的方法由子類來實現,子類執行的結果影響了父類的結果(子類對父類產生了影響),會帶來閱讀程式碼的難度!

1.5模板方法模式JDK應用

最經典的就是JUC包下的AQS(AbstractQueuedSynchronizer)了。AQS是什麼?

AQS其實就是一個可以給我們實現鎖的框架。內部實現的關鍵是:先進先出的佇列、state狀態

我們可以看一下AQS定義的acquire()


    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    

acquire()相當於模板方法,tryAcquire(arg)相當於基本方法。

tryAcquire方法被多個子類實現

最後

模板方法模式也很簡單呀,一個抽象類有基本方法(等著被子類實現的方法),有模板方法(對外暴露、呼叫基本方法、定義了演算法的框架),那就完事了。

推薦閱讀和參考資料:

樂於分享和輸出乾貨的Java技術公眾號:Java3y。關注即可領取海量的視訊資源!

帥的人都關注了

精彩回顧:

覺得我的文章寫得不錯,不妨點一下

相關文章