• 尚硅谷设计模式学习(十四)模板方法模式


    以豆浆制作问题引出模板方法模式

    制作豆浆的流程:选材--->添加配料--->浸泡--->放到豆浆机打碎
    通过添加不同的配料,可以制作出不同口味的豆浆
    选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的

    就得使用模板方法模式

    一、模板方法模式

    1、基本介绍

    在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。

    简单说,模板方法模式定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定步骤。

    这种类型的设计模式属于行为型模式

    2、代码实现

    抽象类AbstractSoyaMilk

    1. public abstract class AbstractSoyaMilk {
    2. //模板方法; 做豆浆的一套模板;
    3. public void template(){
    4. select();
    5. addCondiments();
    6. soak();
    7. beat();
    8. }
    9. public void select(){
    10. System.out.println("挑选配料");
    11. }
    12. public abstract void addCondiments();
    13. public void soak(){
    14. System.out.println("浸泡原料");
    15. }
    16. public void beat(){
    17. System.out.println("豆浆机打碎");
    18. }
    19. }

    两个具体实现类

    1. public class RedBeanSoyaMilk extends AbstractSoyaMilk {
    2. @Override
    3. public void addCondiments() {
    4. System.out.println("添加红豆");
    5. }
    6. }
    1. public class YellowBeanSoyMilk extends AbstractSoyaMilk {
    2. @Override
    3. public void addCondiments() {
    4. System.out.println("添加黄豆");
    5. }
    6. }

    测试

    1. public class Client {
    2. public static void main(String[] args) {
    3. RedBeanSoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();
    4. redBeanSoyaMilk.template();
    5. YellowBeanSoyMilk yellowBeanSoyMilk =new YellowBeanSoyMilk();
    6. yellowBeanSoyMilk.template();
    7. }
    8. }

    结果

    挑选配料
    添加红豆
    浸泡原料
    豆浆机打碎
    挑选配料
    添加黄豆
    浸泡原料
    豆浆机打碎

    3、模板方法模式的钩子方法

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

    还是用上面做豆浆的例子来讲解,比如,我们还希望制作纯豆浆,不添加任何的配料。

    抽象类AbstractSoyaMilk

    1. public abstract class AbstractSoyaMilk {
    2. //模板方法,做豆浆的一套模板
    3. public void template(){
    4. select();
    5. if(customerWantCondiments()){
    6. addCondiments();
    7. }
    8. soak();
    9. beat();
    10. }
    11. public void select(){
    12. System.out.println("挑选配料");
    13. }
    14. public abstract void addCondiments();
    15. public void soak(){
    16. System.out.println("浸泡原料");
    17. }
    18. public void beat(){
    19. System.out.println("豆浆机打碎");
    20. }
    21. //定义钩子方法,默认返回true
    22. public boolean customerWantCondiments(){
    23. return true;
    24. }
    25. }

    子类重写构造方法

    1. public class RedBeanSoyaMilk extends AbstractSoyaMilk {
    2. @Override
    3. public void addCondiments() {
    4. System.out.println("添加红豆");
    5. }
    6. @Override
    7. public boolean customerWantCondiments() {
    8. return false;
    9. }
    10. }

     测试

    1. public class Client {
    2. public static void main(String[] args) {
    3. RedBeanSoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();
    4. redBeanSoyaMilk.template();
    5. }
    6. }

    结果

    挑选配料
    浸泡原料
    豆浆机打碎

    二、模板方法模式在 Spring 框架应用的源码分析 

    Spring IOC  容器初始化时运用到的模板方法模式

    1、查看接口ConfigurableApplicationContext,其中声明了一个模板方法refresh()

    1. public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {
    2. void refresh() throws BeansException, IllegalStateException;
    3. }

    2、抽象类AbstractApplicationContext实现了接口,主要实现了模板方法refresh(这个方法很重要,是各种IOC容器初始化的入口)的逻辑。

    1. public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
    2. /**模板方法的具体实现*/
    3. public void refresh() throws BeansException, IllegalStateException {
    4. synchronized(this.startupShutdownMonitor) {
    5. this.prepareRefresh();
    6. //注意这个方法是,里面调用了两个抽象方法refreshBeanFactory、getBeanFactory
    7. ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
    8. this.prepareBeanFactory(beanFactory);
    9. try {
    10. //注意这个方法是钩子方法
    11. this.postProcessBeanFactory(beanFactory);
    12. this.invokeBeanFactoryPostProcessors(beanFactory);
    13. this.registerBeanPostProcessors(beanFactory);
    14. this.initMessageSource();
    15. this.initApplicationEventMulticaster();
    16. //注意这个方法是钩子方法
    17. this.onRefresh();
    18. this.registerListeners();
    19. this.finishBeanFactoryInitialization(beanFactory);
    20. this.finishRefresh();
    21. } catch (BeansException var9) {
    22. if (this.logger.isWarnEnabled()) {
    23. this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
    24. }
    25. this.destroyBeans();
    26. this.cancelRefresh(var9);
    27. throw var9;
    28. } finally {
    29. this.resetCommonCaches();
    30. }
    31. }
    32. }

    这里最主要有一个抽象方法obtainFreshBeanFactory、两个钩子方法postProcessBeanFactory和onRefresh,看看他们在类中的定义。 

    两个钩子方法

    1. protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    2. }
    3. protected void onRefresh() throws BeansException {
    4. }

     再看看获取Spring容器的抽象方法,内部只调用了两个抽象方法

    1. protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
    2. this.refreshBeanFactory();
    3. return this.getBeanFactory();
    4. }
    5. protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
    6. public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;

    具体要取那种BeanFactory容器的决定权交给了子类! 

    3、具体实现的子类,实现了抽象方法getBeanFactory的子类有:

    GenericApplicationContext

    1. public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
    2. private final DefaultListableBeanFactory beanFactory;
    3. public GenericApplicationContext() {
    4. this.customClassLoader = false;
    5. this.refreshed = new AtomicBoolean();
    6. this.beanFactory = new DefaultListableBeanFactory();
    7. }
    8. protected final void refreshBeanFactory() throws IllegalStateException {
    9. if (!this.refreshed.compareAndSet(false, true)) {
    10. throw new IllegalStateException("GenericApplicationContext does not support multiple refresh attempts: just call 'refresh' once");
    11. } else {
    12. this.beanFactory.setSerializationId(this.getId());
    13. }
    14. }
    15. public final ConfigurableListableBeanFactory getBeanFactory() {
    16. return this.beanFactory;
    17. }
    18. }

    AbstractRefreshableApplicationContext:

    1. public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {
    2. protected final void refreshBeanFactory() throws BeansException {
    3. if (this.hasBeanFactory()) {
    4. this.destroyBeans();
    5. this.closeBeanFactory();
    6. }
    7. try {
    8. DefaultListableBeanFactory beanFactory = this.createBeanFactory();
    9. beanFactory.setSerializationId(this.getId());
    10. this.customizeBeanFactory(beanFactory);
    11. this.loadBeanDefinitions(beanFactory);
    12. synchronized(this.beanFactoryMonitor) {
    13. this.beanFactory = beanFactory;
    14. }
    15. } catch (IOException var5) {
    16. throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var5);
    17. }
    18. }
    19. public final ConfigurableListableBeanFactory getBeanFactory() {
    20. synchronized(this.beanFactoryMonitor) {
    21. if (this.beanFactory == null) {
    22. throw new IllegalStateException("BeanFactory not initialized or already closed - call 'refresh' before accessing beans via the ApplicationContext");
    23. } else {
    24. return this.beanFactory;
    25. }
    26. }
    27. }
    28. }

    三、模板方法模式的注意事项和细节

    1)基本思想:算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改。

    2)实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用。

    3)既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现。

    4)该模式的不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大。

    5)一般模板方法都加上  final  关键字,防止子类重写模板方法。

    模板方法模式使用场景

    当要完成在某个过程,该过程要执行一系列步骤,这一系列的步骤基本相同,但其个别步骤在实现时    可能不同,通常考虑用模板方法模式来处理。

  • 相关阅读:
    很强,我终于找到绘制E-R图的正确姿势
    python的任务调度问题
    【从零学习python 】85.Python进程池的并行计算技术应用
    (附源码)spring boot工作计划管理软件 毕业设计 181638
    SpringBoot项目打包成jar后,使用ClassPathResource获取classpath(resource)下文件失败
    中国单反相机行业供需趋势及投资风险研究报告
    华为OD机考算法题:食堂供餐
    船舶物资与市场杂志船舶物资与市场杂志社船舶物资与市场编辑部2022年第7期目录
    前端文件上传
    异步复位的串联T触发器
  • 原文地址:https://blog.csdn.net/qq_51409098/article/details/126918936