模板方法模式
概述
《设计模式》一书中对于 “模板方法模式” 的意图描述如下:
定义一个操作中的算法骨架,而将一些步骤延迟到子类中
一般在以下几种情况下使用模板方法模式:
- 一次性实现一个算法的不可变部分,并将可变的行为留给子类来实现
- 各个子类中的公共行为应当被提取出来并集中到一个公共父类中以避免重复代码(提炼父类)
- 控制子类的扩展。模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展
具体示例
在 Java
中比较经典的是 AQS
(AbstractQueuedSynchronizer
),这是 Java
中所有同步工具类都需要使用到的同步框架,具体同步工具,如 Lock
、ReadWriteLock
和 CountDownLatch
都是通过继承这个类并改变一些默认的实现来达到对应的目的
比如,如果希望自定义一个互斥锁,那么只需要继承 AQS
并重写 tryAcquire
和 tryRelease
方法即可:
public class SelfLock {
// 实际执行同步操作的同步类
private final class Sync extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquired(int arg) { // 这个方法用于检测是否成功获取锁
Thread t = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, arg)) {
setExclusiveOwnerThread(t);
return true;
}
} else if (t == getExclusiveOwnerThread()) {
setState(c + arg);
return true;
}
return false;
}
@Override
protected boolean tryRelease(int arg) { // 这个方法的目的是检查已经获取锁的线程是否完全释放了锁
int c = getState() - arg;
boolean res = false;
if (c == 0) {
setExclusiveOwnerThread(null);
res = true;
}
setState(c);
return res;
}
}
private final Sync sync = new Sync();
public void lock() {
sync.acquire(1); // 实际通过 AQS 来获取锁,同时由 AQS 维护同步队列状态
}
public void unlock() {
sync.release(1); // 实际调用 AQS 来释放锁,并维护同步队列状态
}
}
而在 AQS
内部,acquire
和 release
方法如下:
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
// 省略部分其它方法
public final void acquire(int arg) {
/*
只要 tryAcquire 方法返回 false,就将当前线程封装成一个节点放入 AQS 的阻塞队列中进行后续的处理,
否则将视为成功获取到锁
*/
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
public final boolean release(int arg) {
/*
只要 tryRelease 方法返回 false,就视为释放锁失败,当返回 true 时视为释放所成功,
同时唤醒后继节点
*/
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
}
由于篇幅原因,具体 AQS
的处理细节在此省略,感兴趣的话可以看看 AQS 解析,本文仅简要介绍其中有关模板方法模式的使用
总结
通过模板方法模式,可以封装相关的固定算法,而将一些细节交给子类来完成,这一设计模式在实际开发过程中也特别常见,特别是在实现框架类的需求时
参考:
[1] 《设计模式—可复用面向对象基础》