java 设计模式之模板方法模式

发布于:2025-04-20 ⋅ 阅读:(19) ⋅ 点赞:(0)

简介

模板方法模式:定义一个算法的基本流程,将一些步骤延迟到子类中实现。模板方法模式可以提高代码的复用性,

模板方法中包含的角色:

  • 抽象类:负责给出一个算法的基本流程,它由一个模板方法和若干个基本方法构成
    • 模板方法:定义了算法的基本流程
    • 基本方法:模板方法的具体步骤,基本方法是可以被抽象到父类中的公共方法,多个子类可以同时使用的方法就是基本方法
    • 抽象方法:需要交给子类实现的方法,不同子类有着不同的实现
  • 具体子类:实现抽象类中定义的抽象方法

适用场景:适合于算法的整体步骤是固定的,但是其中个别部分容易变化,不同子类有着不同实现。

模板方法模式的实现

案例:一个小demo,厨师炒菜,炒菜的顺序是不变的,不同厨师使用不同的菜

第一步:抽象类,在模板方法中定义整体流程

public abstract class AbstractTemplate {

    // 模板方法:厨师炒菜的基本流程
    public void cookProcess(){
        pourOil(); // 倒油
        heatOil(); // 热油
        pourVegetable();  // 放菜
        pourSauce();   // 放调料
        fry();  // 炒菜
    }

    // 基本方法
    private void pourOil(){
        System.out.println("倒油");
    }

    private void heatOil(){
        System.out.println("热油");
    }

    private void fry() {
        System.out.println("炒菜");
    }

    // 交给子类实现的抽象方法
    protected abstract void pourVegetable();

    protected abstract void pourSauce();
}

第二步:具体子类实现父类中的抽象方法

// 具体子类:厨师A
public class ConcreteClassBaoCai extends AbstractTemplate {
    private String name;

    public ConcreteClassBaoCai() { }

    public ConcreteClassBaoCai(String name) {
        this.name = name;
    }

    @Override
    public void pourVegetable() {
        System.out.println(name + ": 下锅的蔬菜是包菜");
    }

    @Override
    public void pourSauce() {
        System.out.println(name + ": 下锅的调料是辣椒");
    }
}

// 具体子类:厨师B
public class ConcreteClassCaiXin extends AbstractTemplate {
    private String name;

    public ConcreteClassCaiXin() { }

    public ConcreteClassCaiXin(String name) {
        this.name = name;
    }

    @Override
    public void pourVegetable() {
        System.out.println(name + ": 下锅的蔬菜是菜心");
    }

    @Override
    public void pourSauce() {
        System.out.println(name + ": 下锅的调料是醋");
    }
}

测试:

public class Client {
    public static void main(String[] args) {
        ConcreteClassBaoCai classBaoCai = new ConcreteClassBaoCai("厨师A");
        classBaoCai.cookProcess();

        ConcreteClassCaiXin classCaiXin = new ConcreteClassCaiXin("厨师B");
        classCaiXin.cookProcess();
    }
}

使用案例

jdk中的juc包,里面就使用到了模板方法设计模式。具体来说,juc包中提供了一系列的锁工具,无论是什么类型的锁,它的共有流程都是获取锁成功后改变锁标志状态然后向下执行、获取锁失败后进入阻塞队列、锁释放后唤醒阻塞队列中的头结点,不同类型的锁,例如公平锁、非公平锁,它们是获取锁的方式不同,例如,公平锁如果发现阻塞队列中有值,会加入阻塞队列,非公平锁会直接获取锁,获取失败后才会加入阻塞队列,所以juc把锁相关的共有流程提取到了aqs中,把不同类型的锁独有的特性放到了具体的锁实现类中。接下来做一个详细介绍

1、juc中的模板方法:aqs中定义了获取锁的整体流程

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {

    // 这就是一个模板方法,定义了获取锁的流程
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&  // 获取锁,成功后返回true,然后向下执行
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 获取锁失败,进入阻塞队列
            selfInterrupt();
    }

    // 交给子类实现的抽象方法,定义了获取锁的方式
    protected boolean tryAcquire(int arg) {
        // 这里的设计值得借鉴,在空方法中抛异常,避免外部误调用
        throw new UnsupportedOperationException();
    }

    // 父类中实现的具体方法,定义了获取锁失败后进入进入阻塞队列的方式,
    // 无论哪种类型的锁,进入阻塞队列的方式都是一样的
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
}

2、子类的具体实现

实现方式1:ReentrantLock中公平锁的实现方式

static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    final void lock() {
        acquire(1);
    }

    // 这里就是重写了上面父类中的tryAquire方法,定义了公平锁获取锁的方式
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {  // 锁没有被获取到
            if (!hasQueuedPredecessors() &&  // 队列中是否有元素
                compareAndSetState(0, acquires)) {  // 如果没有,获取锁
                setExclusiveOwnerThread(current);  // 获取锁成功后,当前线程设置为独占线程
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {  // 如果当前线程已经获取到了锁
            int nextc = c + acquires;  // 可重入的逻辑
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}

实现方式2:ReentrantLock中非公平锁的实现方式

static final class NonfairSync extends Sync {

    // 这里重写了父类中的tryAcquire方法,定义了非公平锁的获取方式
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }

    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;
    }
}

总结:这里顺便介绍了公平锁和非公平锁的实现,可以看出,唯一的区别在于,非公平锁在会直接尝试获取锁,公平锁会先判断阻塞队列中有没有元素,如果没有,再去获取锁,否则加入阻塞队列。

总结

模板方法设计模式,它的使用场景是,如果有多个类似的实体,它们有着共同的流程,每个实体又都有自己独特的地方,可以把它们相同的功能抽取出来,设计成一个模板方法,然后子类负责实现某些关键步骤,例如,之前的公平锁和非公平锁,它们获取锁的方式不同,但是获取锁失败后进入阻塞队列的方式是一样的。


网站公告

今日签到

点亮在社区的每一天
去签到