设计模式之模板方法

前言

本文介绍一下结构型模式中的模板方法。

模板模式是什么

定义一个操作的一系列步骤,对于某些暂时确定不下来的步骤,就留给子类去实现好了,这样不同的子类就可以定义出不同的步骤。通过继承的方式来实现模板 ,完成对父类的填充。

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 设计模式 中文版》
原文地址:https://www.cnblogs.com/wei57960/p/12879436.html