模板方法模式
模板方法模式属于类的行为模式。其核心是定义一个操作中的算法骨架,而将一些步骤延迟到子类中。
模板方法使得子类可以不行一个算法的结构即可重定义该算法的某些特定步骤。
一、范例之InputStream
首先,我们先看下InputStream
我这边简单画了下inputStream的结构,见下图:
我们可以看到,InputStream
实现了Cloaseable
接口,但是InputStream
中的close方法是如何实现的呢?我们可以看下源码
public void close() throws IOException {}
空实现,没做任务事情,为什么这样呢,我们下面再说。
继续看InputStream
中另外一个关键的方法read
,我们可以看到read根据参数的不同,分为了三个方法
首先,我们看下无参的read方法怎么写的
public abstract int read() throws IOException;
静态方法,需要子类实现。
另外两个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;
}
细节我们不去管,我们可以看到,两个方法的具体实现都依赖于无参的read方法。
接下来,我们看下FileInputStream
是如何实现的?
- 首先,我们看下
close
的实现
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
方法
- 接下来,我们看下比较关键的
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
通过调用native
方法,实现了无参的read
方法。另外两个read方法也做了重写,通过native
方法实现。
下面我们再找个子类,PipedInputStream
是如何实现的?
-
close
方法的实现
public void close() throws IOException {
closedByReader = true;
synchronized (this) {
in = -1;
}
}
-
read
方法的实现
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
的父类,它定义了一些规范,要求继承的子类按照找个规范进行开发。
它提供了一些实现,如果满足子类的需要,可实现复用,子类只需要该其中关键的实现点即可。
比如必须要实现的read()
方法(静态方法),可重写的close()
方法。
int read(byte b[], int off, int len) throws IOException
如inputStream
中的查指定范围的read方法,它已经实现了大部分的功能点,但是还有部分依赖于无参的read
方法。所以如果父类功能满足,子类要想实现完整的read
功能,只需要实现无参的read
方法即可。
现在,我们再回过头来看定义一个操作中的算法骨架,而将一些步骤延迟到子类中。
是不是好理解一些。
二、优缺点
- 优点
- 封装不变的部分,扩展可变的部分
- 父类控制行为,子类负责实现
- 缺点
子类太多的话,会使系统更加庞大
三、使用场景
- 多个子类有公有的方法,并且逻辑基本相同
- 重要复杂的算法,可以吧核心设计成模板方法,相关细节功能由各个子类实现
- 重构时,可以将相同的代码抽取到父类,然后通过钩子函数约束其行为
下面引用一下 23种设计模式之模板方法模式中有关钩子函数的例子,见下面代码:
package com.mk.designDemo.designs.template;
public abstract class AbstractTemplateClass {
public void execute() {
// 一些通用逻辑
this.methodA();
if (isRun()) {
System.out.println("执行一些业务逻辑");
}
this.methodB();
}
abstract void methodA();
abstract void methodB();
abstract boolean isRun();
}
子类通过实现isRun()
方法,不同的实现影响了公共代码的执行逻辑,该方法就叫钩子方法。