每日一發(fā)設(shè)計模式 - 模板方法模式(template method)

模板方法模式

模板方法模式屬于類的行為模式。其核心是定義一個操作中的算法骨架,而將一些步驟延遲到子類中。

模板方法使得子類可以不行一個算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟。

一、范例之InputStream

首先,我們先看下InputStream

我這邊簡單畫了下inputStream的結(jié)構(gòu),見下圖:


inputStream

我們可以看到,InputStream實現(xiàn)了Cloaseable接口,但是InputStream中的close方法是如何實現(xiàn)的呢?我們可以看下源碼

public void close() throws IOException {}

空實現(xiàn),沒做任務(wù)事情,為什么這樣呢,我們下面再說。
繼續(xù)看InputStream中另外一個關(guān)鍵的方法read,我們可以看到read根據(jù)參數(shù)的不同,分為了三個方法
首先,我們看下無參的read方法怎么寫的

public abstract int read() throws IOException;

靜態(tài)方法,需要子類實現(xiàn)。
另外兩個read方法分別如下:

public int read(byte b[]) throws IOException {
    return read(b, 0, b.length);
}
public int read(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }

        int c = read();
        if (c == -1) {
            return -1;
        }
        b[off] = (byte)c;

        int i = 1;
        try {
            for (; i < len ; i++) {
                c = read();
                if (c == -1) {
                    break;
                }
                b[off + i] = (byte)c;
            }
        } catch (IOException ee) {
        }
        return i;
 }

細節(jié)我們不去管,我們可以看到,兩個方法的具體實現(xiàn)都依賴于無參的read方法。

接下來,我們看下FileInputStream是如何實現(xiàn)的?
  • 首先,我們看下close的實現(xiàn)
    public void close() throws IOException {
        synchronized (closeLock) {
            if (closed) {
                return;
            }
            closed = true;
        }
        if (channel != null) {
           channel.close();
        }

        fd.closeAll(new Closeable() {
            public void close() throws IOException {
               close0();
           }
        });
    }

由上述代碼可以看到,FileInputStream作為子類,覆蓋了父類中的空的close方法

  • 接下來,我們看下比較關(guān)鍵的read方法
    public int read() throws IOException {
        return read0();
    }

    private native int read0() throws IOException;

    private native int readBytes(byte b[], int off, int len) throws IOException;

    public int read(byte b[], int off, int len) throws IOException {
        return readBytes(b, off, len);
    }

    public int read(byte b[]) throws IOException {
        return readBytes(b, 0, b.length);
    }

我們可以看到,FileInputStream通過調(diào)用native方法,實現(xiàn)了無參的read方法。另外兩個read方法也做了重寫,通過native方法實現(xiàn)。

下面我們再找個子類,PipedInputStream是如何實現(xiàn)的?
  • close方法的實現(xiàn)
    public void close()  throws IOException {
        closedByReader = true;
        synchronized (this) {
            in = -1;
        }
    }
  • read方法的實現(xiàn)
public synchronized int read()  throws IOException {
        if (!connected) {
            throw new IOException("Pipe not connected");
        } else if (closedByReader) {
            throw new IOException("Pipe closed");
        } else if (writeSide != null && !writeSide.isAlive()
                   && !closedByWriter && (in < 0)) {
            throw new IOException("Write end dead");
        }

        readSide = Thread.currentThread();
        int trials = 2;
        while (in < 0) {
            if (closedByWriter) {
                /* closed by writer, return EOF */
                return -1;
            }
            if ((writeSide != null) && (!writeSide.isAlive()) && (--trials < 0)) {
                throw new IOException("Pipe broken");
            }
            /* might be a writer waiting */
            notifyAll();
            try {
                wait(1000);
            } catch (InterruptedException ex) {
                throw new java.io.InterruptedIOException();
            }
        }
        int ret = buffer[out++] & 0xFF;
        if (out >= buffer.length) {
            out = 0;
        }
        if (in == out) {
            /* now empty */
            in = -1;
        }

        return ret;
    }
綜上,我們可以看出?

InputStream作為一個abstract的父類,它定義了一些規(guī)范,要求繼承的子類按照找個規(guī)范進行開發(fā)。
它提供了一些實現(xiàn),如果滿足子類的需要,可實現(xiàn)復(fù)用,子類只需要該其中關(guān)鍵的實現(xiàn)點即可。
比如必須要實現(xiàn)的read()方法(靜態(tài)方法),可重寫的close()方法。

int read(byte b[], int off, int len) throws IOException

inputStream中的查指定范圍的read方法,它已經(jīng)實現(xiàn)了大部分的功能點,但是還有部分依賴于無參的read方法。所以如果父類功能滿足,子類要想實現(xiàn)完整的read功能,只需要實現(xiàn)無參的read方法即可。

現(xiàn)在,我們再回過頭來看定義一個操作中的算法骨架,而將一些步驟延遲到子類中。是不是好理解一些。

二、優(yōu)缺點

  • 優(yōu)點
    1. 封裝不變的部分,擴展可變的部分
    2. 父類控制行為,子類負責(zé)實現(xiàn)
  • 缺點
    子類太多的話,會使系統(tǒng)更加龐大

三、使用場景

  1. 多個子類有公有的方法,并且邏輯基本相同
  2. 重要復(fù)雜的算法,可以吧核心設(shè)計成模板方法,相關(guān)細節(jié)功能由各個子類實現(xiàn)
  3. 重構(gòu)時,可以將相同的代碼抽取到父類,然后通過鉤子函數(shù)約束其行為

下面引用一下 23種設(shè)計模式之模板方法模式中有關(guān)鉤子函數(shù)的例子,見下面代碼:

package com.mk.designDemo.designs.template;

public abstract class AbstractTemplateClass {

    public void execute() {
        // 一些通用邏輯
        this.methodA();
        if (isRun()) {
            System.out.println("執(zhí)行一些業(yè)務(wù)邏輯");
        }
        this.methodB();
    }

    abstract void methodA();

    abstract void methodB();

    abstract boolean isRun();

}

子類通過實現(xiàn)isRun()方法,不同的實現(xiàn)影響了公共代碼的執(zhí)行邏輯,該方法就叫鉤子方法

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容