AnthonyZero's Bolg

设计模式:模板方法模式

定义

使用JAVA的继承机制,在抽象类中定义一个模板方法,该方法引用了若干个抽象方法(由子类实现)或具体方法(子类可以覆盖重写)

模板方法模式是类的行为模式。准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。这就是模板方法模式的用意

模板方法模式是结构最简单的行为型设计模式,在其结构中只存在父类与子类之间的继承关系。通过使用模板方法模式,可以将一些复杂流程的实现步骤封装在一系列基本方法中,在抽象父类中提供一个称之为模板方法的方法来定义这些基本方法的执行次序,而通过其子类来覆盖某些步骤,从而使得相同的算法框架可以有不同的执行结果

UML图

Alt text

这里涉及到两个角色:
抽象模板(Abstract Template)角色有如下责任:

  • 定义了一个或多个抽象操作,以便让子类实现。这些抽象操作叫做基本操作,它们是一个顶级逻辑的组成步骤
  • 定义并实现了一个模板方法。这个模板方法一般是一个具体方法,它给出了一个顶级逻辑的骨架,而逻辑的组成步骤在相应的抽象操作中,推迟到子类实现。顶级逻辑也有可能调用一些具体方法。

具体模板(Concrete Template)角色有如下责任:

  • 实现父类所定义的一个或多个抽象方法,它们是一个顶级逻辑的组成步骤
  • 每一个抽象模板角色都可以有任意多个具体模板角色与之对应,而每一个具体模板角色都可以给出这些抽象方法(也就是顶级逻辑的组成步骤)的不同实现,从而使得顶级逻辑的实现各不相同。

示例

冲咖啡和冲茶都有类似的流程,但是某些步骤会有点不一样,要求复用那些相同步骤的代码。

public abstract class AbstractTemplate {

    /**
     * 模板方法 定义了逻辑的组成步骤
     */
    public final void templateMethod(){
        concreteMethod(); //模板类自己的具体方法实现
        abstractMethod(); //子类的实现
        hookMethod();     //钩子方法:子类选择性的实现
    }
    /**
     * 基本方法的声明(由子类必须实现)
     */
    protected abstract void abstractMethod();
    /**
     * 基本方法(空方法) 子类选择性的覆盖重写
     */
    protected void hookMethod(){}
    /**
     * 基本方法(已经实现)
     */
    private void concreteMethod(){
        System.out.println("倒水");
    }
}

喝咖啡步骤:

public class CoffeeTemplate extends AbstractTemplate {
    @Override
    protected void abstractMethod() {
        System.out.println("准备咖啡倒入");
    }
}

喝茶步骤:

public class TeaTemplate extends AbstractTemplate {
    @Override
    protected void abstractMethod() {
        System.out.println("准备茶叶倒入");
    }

    @Override
    protected void hookMethod() {
        System.out.println("还准备了蜂蜜倒入");
    }
}

使用输出:

public class Client {
    public static void main(String[] args) {
        AbstractTemplate coffee = new CoffeeTemplate();
        coffee.templateMethod();
        System.out.println("******************");
        AbstractTemplate tea = new TeaTemplate();
        tea.templateMethod();
    }
}

倒水
准备咖啡倒入
******************
倒水
准备茶叶倒入
还准备了蜂蜜倒入

扩展:AQS的模板方法设计模式

AQS的设计是使用模板方法设计模式,它将一些方法开放给子类进行重写,而同步器给同步组件所提供模板方法又会重新调用被子类所重写的方法。举个例子,AQS中需要子类重写tryAcquire方法

protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

ReentrantLock中的NonfairSync非公平锁(继承AQS)会重写该方法

static final class NonfairSync extends Sync {
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }
    // 子类重写同步器的tryAcquire 方法
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

// NonfairSync 继承 Sync 继承 同步器AbstractQueuedSynchronizer
abstract static class Sync extends AbstractQueuedSynchronizer {

    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}

AQS中的模板方法acquire():

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

ReentrantLock当加锁的时候(默认是非公平锁),会调用内部类NonfairSync的lock方法,lock方法会调用AQS提供的模板方法acquire,AQS模板方法中反过来就会调用已经被NonfairSync重写的tryAcquire方法,这就是模板方法设计模式在AQS中的体现。