模板方法模式
模板方法模式屬于類的行為模式。其核心是定義一個操作中的算法骨架,而將一些步驟延遲到子類中。
模板方法使得子類可以不行一個算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟。
一、范例之InputStream
首先,我們先看下InputStream
我這邊簡單畫了下inputStream的結(jié)構(gòu),見下圖:

我們可以看到,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)點
- 封裝不變的部分,擴展可變的部分
- 父類控制行為,子類負責(zé)實現(xiàn)
- 缺點
子類太多的話,會使系統(tǒng)更加龐大
三、使用場景
- 多個子類有公有的方法,并且邏輯基本相同
- 重要復(fù)雜的算法,可以吧核心設(shè)計成模板方法,相關(guān)細節(jié)功能由各個子類實現(xiàn)
- 重構(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í)行邏輯,該方法就叫鉤子方法。