前言
本文介绍一下结构型模式中的模板方法。
模板模式是什么
定义一个操作的一系列步骤,对于某些暂时确定不下来的步骤,就留给子类去实现好了,这样不同的子类就可以定义出不同的步骤。通过继承的方式来实现模板 ,完成对父类的填充。
UML
代码案例
public abstract class Greet {
// 定义的模板方法,它作为算法的模板,final防止子类覆盖此方法
// 某些方法是自己执行 某些方法是子类处理的
final void all(){
hello();
bey();
sleep();
}
protected abstract void hello();
protected abstract void bey();
// 相同部分
// 也可以定义为 final 防止子类覆写,直接被模板方法使用或者让子类使用
void sleep (){
System.out.println("sleeping");
}
// 定义一个什么都不做的方法(或者默认的缺省实现),这种方法通常称为"hook" 钩子。子类自己视情况来决定要不要覆盖它们
void hock(){}
}
钩子的用途:
- 让子类更加灵活的实现逻辑,控制模板方法中的流程。
- 可以让子类对模板方法中某些即将发生的(或刚刚发生的)步骤做出反应(有点AOP的意思)
使用钩子的场景:如上所说,如果模板算法中的某些步骤子类必须实现的话,就是用抽象算法。如果算法中的这个部分是可选的,就用钩子。
// 通过手机打招呼
public class PhoneGreet extends Greet{
@Override
protected void hello() {
System.out.println(" hello by phone ");
}
@Override
protected void bey() {
System.out.println(" bey by phone ");
}
}
// 通过言语打招呼
public class WordsGreet extends Greet {
@Override
protected void hello() {
System.out.println("hello by mouth");
}
@Override
protected void bey() {
System.out.println("bey by mouth");
}
}
// 测试类
public class TemplateTest {
public static void main(String[] args) {
PhoneGreet greet = new PhoneGreet();
// 调用模板方法
greet.all();
}
}
这样看来跟父类定义接口有什么区别?其中某些操作还是父类来进行的。
JDK AbstractQueuedSynchronizer (AQS) 的使用
仅简单介绍一下AQS 的使用,未涉及具体源码分析。后续单独写AQS的源码设计思路。
AQS 是用来构建锁或者其他同步组件的基础框架。锁是面向使用者的,隐藏了实现细节,AQS是面向锁的实现者的,简化了锁的实现方式,对底层实现进行屏蔽。AQS的设计就是基于模板方法模式的。使用者需要继承同步器,并重写指定的方法。比如 CountDownLatch、Semaphore、RenntrantLock。
下面代码演示 AQS 的使用
// 自定义同步组件 在同一时刻只允许一个线程占有锁
public class NewLock implements Lock {
private static final int lockNum = 1;
class Sync extends AbstractQueuedSynchronizer {
// 是否处于独占状态
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
// 当状态为 0 的时候获取锁
@Override
public boolean tryAcquire(int acquire) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// 释放锁
@Override
protected boolean tryRelease(int arg) {
if (getState() == 0) {
throw new IllegalMonitorStateException();
}
setExclusiveOwnerThread(null);
setState(0);
return true;
}
// 返回Condition 每个Condition 都包含一个 condition 队列
Condition newCondition() {
return new ConditionObject();
}
}
// 将锁的操作代理到 Sync 上
private final Sync sync = new Sync();
// 阻塞获取锁
@Override
public void lock() {
// acquire 会调用重写的 tryAcquire(int arg)
sync.acquire(lockNum);
}
/**
* 可中断地获取锁
*
* @throws InterruptedException
*/
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(lockNum);
}
// 尝试非阻塞的获取锁
@Override
public boolean tryLock() {
return sync.tryAcquire(lockNum);
}
// 超时获取锁
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(lockNum, unit.toNanos(time));
}
// 释放锁
@Override
public void unlock() {
sync.release(lockNum);
}
// 获取等待通知组件
@Override
public Condition newCondition() {
return sync.newCondition();
}
// 是否获取到了锁
public boolean isLocked(){
return sync.isHeldExclusively();
}
// 是否有其他线程在等待获取锁
public boolean hasQueuedThreads(){
return sync.hasQueuedThreads();
}
}
总结
模板方法就是父类定义一系列的操作(模板),由子类来实现某些操作。这样可以规范子类提供某些步骤的实现。
父类可以通过 final 来定义不想被修改的骨架。对于需要子类实现的抽象方法,一般声明为protected 这样可以使这些方法对外部不可以见,并且只用于子类继承可见。
References
- 《Java并发编程的艺术》
- 《HEAD FIRST 设计模式 中文版》