设计模式——模板模式

模板方法模式是一种行为设计模式,定义了算法的骨架并在抽象类中实现,允许子类替换具体步骤。文章通过制作豆浆的例子展示了模板方法如何工作,包括基本介绍、解决豆浆制作问题的示例,以及钩子方法的概念。此外,还讨论了Spring框架中AbstractApplicationContext的refresh()方法作为模板方法的应用,以及JUC包下AQS抽象队列同步器在多线程同步中的角色。
摘要由CSDN通过智能技术生成

  导航:   

【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析

目录

模板模式

1、基本介绍

2、模板模式解决豆浆制作问题

3、钩子方法

4、Spring 框架AbstractApplicationContext抽象类

5、JUC包下的AQS抽象队列同步器

6、应用场景:AQS 


模板模式

1、基本介绍

  • 1)模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern),在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行
  • 2)简单说,模板方法模式定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定步骤
  • 3)这种类型的设计模式属于行为型模式(用于描述对象之间的通信和责任分配)。

实现方式:抽象类有一个模板方法和其他行为方法,模板方法按流程调用各行为方法(抽象或非抽象);具体子类重写抽象的行为方法。

对原理类图的说明——即模板方法模式的角色和职责

  • AbstractClass抽象类中实现了模板方法,定义了算法的骨架,具体子类需要去实现其抽象方法或重写其中方法
  • ConcreteClass实现了抽象方法,已完成算法中特定子类的步骤

注意事项和细节

  • 1)基本思想:算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改
  • 2)实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用
  • 3)既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现
  • 4)不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大
  • 5)一般模板方法都加上final关键字,防止子类重写模板方法
  • 6)使用场景:当要完成在某个过程,该过程要执行一系列步骤,这一系列的步骤基本相同,但其个别步骤在实现时可能不同,通常考虑用模板方法模式来处理

2、模板模式解决豆浆制作问题

编写制作豆浆的程序,说明如下:

  • 1)制作豆浆的流程选材 ----> 添加配料 ----> 浸泡 ----> 放到豆浆机打碎
  • 2)通过添加不同的配料,可以制作出不同口味的豆浆
  • 3)选材、浸泡和放到豆浆机打碎这几个步骤是一个模板方法,对于制作每种口味的豆浆都是一样的
  • 4)请使用模板方法模式完成

说明:因为模板方法模式比较简单,很容易就想到这个方案,因此就直接使用,不再使用传统的方案来引出模板方法模式

核心代码

/**
 * 抽象方法
 */
public abstract class SoyaMilk {
    /**
     * 模板方法,定义为final禁止覆写
     */
    public final void make() {
        System.out.println(">>>>>>豆浆制作开始<<<<<<");
        useSoyBean();
        addIngredients();
        soak();
        mash();
        System.out.println(">>>>>>豆浆制作结束<<<<<<");
    }
// 同包可见、对其他包下的子类可见。
    protected void useSoyBean() {
        System.out.println("Step1. 选用上好的黄豆.");
    }
//添加原材料是抽象方法,因为不同豆浆原材料不一样
    protected abstract void addIngredients();

    protected void soak() {
        System.out.println("Step3. 对黄豆和配料进行水洗浸泡.");
    }

    protected void mash() {
        System.out.println("Step4. 将充分浸泡过的黄豆和配料放入豆浆机中,开始打豆浆.");
    }
}
/**
 * 花生豆浆
 */
public class PeanutSoyaMilk extends SoyaMilk {
    public PeanutSoyaMilk() {
        System.out.println("============花生豆浆============");
    }
    @Override
    protected void addIngredients() {
        System.out.println("Step2. 加入上好的花生.");
    }
}
/**
 * 红豆豆浆
 */
public class RedBeanSoyaMilk extends SoyaMilk {
    public RedBeanSoyaMilk() {
        System.out.println("============红豆豆浆============");
    }
    @Override
    protected void addIngredients() {
        System.out.println("Step2. 加入上好的红豆.");
    }
}
/**
 * 芝麻豆浆
 */
public class SesameSoyaMilk extends SoyaMilk {
        public SesameSoyaMilk() {
        System.out.println("============芝麻豆浆============");
    }
    @Override
    protected void addIngredients() {
        System.out.println("Step2. 加入上好的芝麻.");
    }
}

客户端调用模板方法

SoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();
peanutSoyaMilk.make();
SoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();
redBeanSoyaMilk.make();
SoyaMilk sesameSoyaMilk = new SesameSoyaMilk();
sesameSoyaMilk.make();
/*
============花生豆浆============
>>>>>>豆浆制作开始<<<<<<
Step1. 选用上好的黄豆.
Step2. 加入上好的花生.
Step3. 对黄豆和配料进行水洗浸泡.
Step4. 将充分浸泡过的黄豆和配料放入豆浆机中,开始打豆浆.
>>>>>>豆浆制作结束<<<<<<
============红豆豆浆============
>>>>>>豆浆制作开始<<<<<<
Step1. 选用上好的黄豆.
Step2. 加入上好的红豆.
Step3. 对黄豆和配料进行水洗浸泡.
Step4. 将充分浸泡过的黄豆和配料放入豆浆机中,开始打豆浆.
>>>>>>豆浆制作结束<<<<<<
============芝麻豆浆============
>>>>>>豆浆制作开始<<<<<<
Step1. 选用上好的黄豆.
Step2. 加入上好的芝麻.
Step3. 对黄豆和配料进行水洗浸泡.
Step4. 将充分浸泡过的黄豆和配料放入豆浆机中,开始打豆浆.
>>>>>>豆浆制作结束<<<<<<
*/

3、钩子方法

在模板方法模式的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为“钩子”

还是用上面做豆浆的例子来讲解,比如,我们还希望制作纯豆浆,不添加任何的配料,请使用钩子方法对前面的模板方法进行改造

抽象类和具体类

//抽象模板类
public abstract class SoyaMilk {
//模板方法
    public final void make() {
        // ...
//如果钩子方法决定加配料,就加配料;否则不执行加配料操作。
        if (customAddIngredients()) {
            addIngredients();
        }
        // ...
    }
//钩子方法,决定是否需要添加配料。默认情况是加配料。
    boolean customAddIngredients() {
        return true;
    }
    // ...
}

/**
 * 纯豆浆
 */
public class PureSoyaMilk extends SoyaMilk {
    public PureSoyaMilk() {
        System.out.println("============纯豆浆============");
    }

    @Override
    protected void addIngredients() {
        // 空实现即可
    }

    @Override
    protected Boolean customAddIngredients() {
        return false;
    }
}

客户端,测试钩子方法

SoyaMilk pureSoyaMilk = new PureSoyaMilk();
pureSoyaMilk.make();
/*
============纯豆浆============
>>>>>>豆浆制作开始<<<<<<
Step1. 选用上好的黄豆.
Step3. 对黄豆和配料进行水洗浸泡.
Step4. 将充分浸泡过的黄豆和配料放入豆浆机中,开始打豆浆.
>>>>>>豆浆制作结束<<<<<<
*/

4、Spring 框架AbstractApplicationContext抽象类

AbstractApplicationContext.java中有一个refresh()方法就是模板方法,它用于根据流程调用aop代理创建、bean生命周期初始化、属性注入等启动并初始化Spring应用上下文的方法。

AbstractApplicationContext抽象类是ApplicationContext接口的一种默认实现,提供了一些通用的应用上下文功能,同时也为其他具体的应用上下文实现类提供了一些可扩展的方法。

应用上下文:负责管理各种bean以及它们之间的关系,并对它们进行生命周期的管理。 

// 模板方法
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        prepareRefresh();
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        prepareBeanFactory(beanFactory);
        try {
            postProcessBeanFactory(beanFactory); // 钩子方法
            invokeBeanFactoryPostProcessors(beanFactory);
            registerBeanPostProcessors(beanFactory);
            initMessageSource();
            initApplicationEventMulticaster();
            onRefresh(); // 钩子方法
            registerListeners();
            finishBeanFactoryInitialization(beanFactory);
            finishRefresh();
        }
        catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
            }
            destroyBeans();
            cancelRefresh(ex);
            throw ex;
        }
        finally {
            resetCommonCaches();
        }
    }
}
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
    refreshBeanFactory(); // 抽象方法
    ConfigurableListableBeanFactory beanFactory = getBeanFactory(); // 抽象方法
    if (logger.isDebugEnabled()) {
        logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
    }
    return beanFactory;
}
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
}
protected void onRefresh() throws BeansException {
    // For subclasses: do nothing by default.
}

5、JUC包下的AQS抽象队列同步器

AQS是基于模板方法模式进行设计的。锁的实现类需要继承AQS并重写它指定的方法。

AQS的模板方法将“管理同步状态的逻辑”提炼出来形成标准流程,这些方法主要包括:独占式获取同步状态、独占式释放同步状态、共享式获取同步状态、共享式释放同步状态。

AQS(AbstractQueuedSynchronizer抽象队列同步器):

AQS是实现锁或者其他同步器(用于协调线程之间对共享资源的访问)的核心框架,通过内部的状态变量和队列来实现线程的同步。ReentrantLock,ThreadPoolExecutor,CountDownLatch等都是基于AQS实现。

  • 同步状态:在AQS中,volatile类型的state变量表示锁的状态,通过CAS原子操作这个状态变量来保证线程安全。
  • 同步队列:在AQS中,FIFO(先入先出)队列用来管理等待锁的线程,队列每个节点记录等待线程的状态以及等待锁的条件。先入先出确保同步器的公平性,也就是先等待的线程先获得锁。

实现线程同步的原理:线程通过CAS原子性修改state变量,修改成功则获得锁,失败则插入队尾等待。 

基于模板方法:AQS是基于模板方法模式进行设计的。锁的实现类需要继承AQS并重写它指定的方法。

AQS的模板方法将“管理同步状态的逻辑”提炼出来形成标准流程,这些方法主要包括:独占式获取同步状态、独占式释放同步状态、共享式获取同步状态、共享式释放同步状态。

6、应用场景:AQS 

AQS是基于模板方法模式进行设计的。锁的实现类需要继承AQS并重写它指定的方法。

AQS的模板方法将“管理同步状态的逻辑”提炼出来形成标准流程,这些方法主要包括:独占式获取同步状态、独占式释放同步状态、共享式获取同步状态、共享式释放同步状态。 

AQS(AbstractQueuedSynchronizer抽象队列同步器):

AQS是一个抽象类,在JUC.locks包下,它通过内部的状态变量和同步队列来实现线程的同步(允许多个线程协作共享访问共享资源)。很多锁和同步器都是基于AQS实现的。ReentrantLock,ThreadPoolExecutor,CountDownLatch等都是基于AQS实现。

  • ReentrantLock:可重入锁,同一个线程在持有锁的情况下,可以重复地获取该锁,无需等待,只需记录重入次数。能防止死锁,因为不用线程自己等待自己释放锁。是Lock接口的实现类。可以通过构造参数true或false指定公平锁或非公平锁,可以通过newCondition()方法创建多个Condition对象分组唤醒等待线程。
    • 公平锁:按加锁顺序获取锁。线程竞争锁时判断AQS队列里有没有等待线程,有就加入队尾。
    • 非公平锁(默认):可能某个线程会不断获取锁,牺牲公平的情况下提高了效率。不管AQS队列里有没有等待线程,都会先尝试获取锁;如果抢占不到,再加入队尾。如果线程刚好在上个线程释放时拿到锁,就不用像公平锁那样还要阻塞等待、放队尾、唤醒,这些操作涉及到对内核的切换,对性能有影响。
    • Condition对象:用于线程间通信,通过await()和signal(),signalAll()让线程等待或唤醒。通常用lock锁创建Condition对象,即lock.newCondition();
  • CountDownLatch:计数器,它允许一个或多个线程等待其他线程完成操作后再执行。countDown()方法让计数器减一,await()方法阻塞当前线程直到计数器减为0。
  • ThreadPoolExecutor。

state变量和等待队列:

  • state变量:在AQS中,volatile类型的state变量表示锁的状态,通过CAS原子操作这个状态变量来保证线程安全。初始是0,代表没拿到锁,1代表拿到锁。
  • 同步队列:在AQS中,FIFO(先入先出)队列用来管理等待锁的线程,队列每个节点记录等待锁的线程的地址、状态、等待锁的条件。先入先出确保同步器的公平性,也就是先等待的线程先获得锁。队列底层是双向链表。

实现线程同步的原理:线程通过CAS原子性修改state变量,修改成功则获得锁,失败则插入队尾等待。 

基于模板方法:AQS是基于模板方法模式进行设计的。锁的实现类需要继承AQS并重写它指定的方法。

AQS的模板方法将“管理同步状态的逻辑”提炼出来形成标准流程,这些方法主要包括:独占式获取同步状态、独占式释放同步状态、共享式获取同步状态、共享式释放同步状态。

模板方法:抽象类有一个模板方法和其他行为方法,模板方法按流程调用各行为方法(抽象或非抽象);具体子类重写抽象的行为方法。 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员小海绵【vincewm】

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值