設計模式——模板方法模式

在閱讀 AQS(AbstractQueuedSynchronizer) 的源碼時,看到有文章說 AQS 是模板方法模式的一個典型應用,因此深刻學習一下模板方法模式,但願能更深刻的理解 AQS 並弄懂模板方法模式。java

簡介

模板方法是一種行爲設計模式, 它在超類中定義了一個算法的框架, 容許子類在不修改結構的狀況下重寫算法的特定步驟node

UML 類圖

場景分析

假設咱們須要從文件中讀取數據,將數據排序,最後導出數據到某種類型的文件中。算法

咱們能夠把它們定義成一個方法來處理。設計模式

public abstract class ExportService {

    /** * 處理數據 * step1: 從指定路徑讀取文件中的數據 * step2: 對數據進行排序 * step3: 導出爲文件 */
    public final void processData(String path) {
        Data data = readDataFromPath(path);
        sort(data);
        exportToXlsFile(data);
    }

    private void sort(Data data){
        SortUtil.sort(data);   
    }

    abstract void exportToFile(Data data);

    private Data readDataFromPath(String path) {
        return FileUtil.readDataFromPath(path);
    }

}
複製代碼

processData 方法至關於一個算法流程,該算法分爲三步:框架

  • readDataFromPath(讀取數據)
  • sort(數據排序)
  • exportToFile(導出數據)

每一個步驟都對應一個方法。工具

在這三個步驟中,讀取數據與數據排序一般都有工具類能夠實現。而通常系統的導出功能支持多種格式,csvpdf 等等。因此對於 PDFExportServiceCSVExportService 來講,能夠繼承 ExportService,只實現其中的 exportToFile 方法,另外兩個方法使用默認實現,就能夠實現將原文件數據處理成不一樣格式文件的目的。學習

也注意到,ExportService 類中的 processDatafinal 關鍵字修飾,緣由是對於全部的 ExportService 來講,處理數據的流程是同樣的,子類只是從新定義了該流程中的某一個步驟。ui

JDK 中使用

分析 AQS 中獲取獨佔鎖的方法: acquire(int arg)spa

public final void acquire(int arg) {
        // 判斷分爲兩步
        // step1: 調用 tryAcquire(arg) 嘗試獲取鎖
        // step2: 未獲取成功調用 acquireQueued 不間斷的獲取排他鎖
        if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
            selfInterrupt();
        }
    }

    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (; ; ) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt()) {
                    interrupted = true;
                }
            }
        } finally {
            if (failed) {
                cancelAcquire(node);
            }
        }
    }
複製代碼

acquire 方法被聲明爲 final, 獲取獨佔鎖的流程包括兩步:設計

  • 調用 tryAcquire(arg) 嘗試獲取鎖
  • 未獲取成功調用 acquireQueued 不間斷的獲取排他鎖

其中,tryAcquire(arg) 方法在 AQS 中是直接拋出異常的,因此須要子類重寫,而 acquireQueued 方法有了默認實現且聲明爲 final,子類無需重寫。因此在該方法中,acquire 方法定義了 獲取獨佔鎖的流程,就是 模板方法

與策略模式對比

從做用上來看,模板方法模式與策略模式都是對一個基類的方法有多種不一樣的實現。

而策略模式的具體實現中,採用的是組合 + 接口的策略。而模板方法模式就徹底基於 Java 語言的多態特性實現的繼承。從設計模式的思想來講,組合是優於繼承的。

更大的區別在於,策略模式是從一個功能總體上出發的,每種策略對該功能都有不一樣的實現。而模板方法模式是對一個算法或流程細分爲多個步驟,每一個步驟均可以被子類重寫,可是整個流程是不能被從新定義的。從代碼實現上來說,當模板方法中的須要重寫的步驟佔了絕大部分時,子類只有幾個方法不用重寫,在這種狀況下不如使用策略模式。而當模板方法中須要重寫的步驟很少時,使用策略模式會形成不一樣的實現類中有不少重複的代碼。

Java8 中接口已經支持默認方法,可是無法被 final 修飾,因此無法維護流程的不可變性,沒法用來替代繼承的實現方式。

相關文章
相關標籤/搜索