【设计模式——学习笔记】23种设计模式——模板方法模式Template Method(原理讲解+应用场景介绍+案例介绍+Java代码实现)

简介: 【设计模式——学习笔记】23种设计模式——模板方法模式Template Method(原理讲解+应用场景介绍+案例介绍+Java代码实现)

介绍

基本介绍

  • 模板方法模式,又叫模板模式,在一个抽象类中定义了一个执行它的其他方法的公开模板方法,子类可以按需重写抽象类的抽象方法
  • 简单说,模板方法模式 定义一个操作中的算法(或者说流程)的骨架,而将一些步骤下放到子类中实现,使得子类可以在不改变算法结构的基础上,可以重新定义算法的某些步骤
  • 该模式属于行为型模式

使用说明

【AbstractClass】

  • template方法规定了如何调用operation2operation3operation4这几个子方法,子方法可以是抽象方法(需要子类来实现),也可以是已经实现的方法

【ConcreteClass、ConcreteClassB】

  • 用来实现父类的抽象方法

应用场景

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

登场角色

  • AbstractClass(抽象类):该角色负责实现模板方法,还声明在模板方法中所使用到的抽象方法
  • ConcreteClass(具体类):负责具体实现AbstractClass角色中定义的抽象方法。这里实现的方法将会在AbstractClass角色的模板方法中被调用
  • Client(客户端):使用具体类继承的模板方法

案例实现

案例一

问题介绍

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

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

实现

【豆浆抽象类

package com.atguigu.template;
/**
 * 抽象类,表示豆浆
 */
public abstract class SoyaMilk {
   /**
    * 模板方法, 模板方法可以做成final , 不让子类去覆盖
    */
   final void make() {
      select();
      addCondiments();
      soak();
      beat();
   }
   /**
    * 选材料
    */
   void select() {
      System.out.println("第一步:选择好的新鲜黄豆  ");
   }
   /**
    * 添加不同的配料, 抽象方法, 子类具体实现
    */
   abstract void addCondiments();
   /**
    * 浸泡
    */
   void soak() {
      System.out.println("第三步:黄豆和配料开始浸泡, 需要3小时 ");
   }
   /**
    * 打碎
    */
   void beat() {
      System.out.println("第四步:黄豆和配料放到豆浆机去打碎  ");
   }
}

【红豆豆浆】

package com.atguigu.template;
/**
 * 红豆豆浆
 */
public class RedBeanSoyaMilk extends SoyaMilk {
   @Override
   void addCondiments() {
      System.out.println(" 加入上好的红豆 ");
   }
}

【花生豆浆】

package com.atguigu.template;
/**
 * 花生豆浆
 */
public class PeanutSoyaMilk extends SoyaMilk {
   @Override
   void addCondiments() {
      System.out.println(" 加入上好的花生 ");
   }
}

【主类】

package com.atguigu.template;
public class Client {
   public static void main(String[] args) {
      System.out.println("----制作红豆豆浆----");
      SoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();
      redBeanSoyaMilk.make();
      System.out.println("----制作花生豆浆----");
      SoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();
      peanutSoyaMilk.make();
   }
}

【运行】

----制作红豆豆浆----
第一步:选择好的新鲜黄豆  
 加入上好的红豆 
第三步:黄豆和配料开始浸泡, 需要3小时 
第四步:黄豆和配料放到豆浆机去打碎  
----制作花生豆浆----
第一步:选择好的新鲜黄豆  
 加入上好的花生 
第三步:黄豆和配料开始浸泡, 需要3小时 
第四步:黄豆和配料放到豆浆机去打碎  
Process finished with exit code 0

模板方法模式的钩子方法

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

应用场景:希望可以制作纯豆浆,需要添加任何配料,使用钩子方法改造上面的程序

【豆浆抽象类】

package com.atguigu.template.improve;
//抽象类,表示豆浆
public abstract class SoyaMilk {
   /**
    * 模板方法, make , 模板方法可以做成final , 不让子类去覆盖.
    */
   final void make() {
      select();
      if(customerWantCondiments()) {
         // 如果需要添加配料
         addCondiments();
      }
      soak();
      beat();
   }
   /**
    * 选材料
    */
   void select() {
      System.out.println("第一步:选择好的新鲜黄豆  ");
   }
   /**
    * 添加不同的配料, 抽象方法, 子类具体实现
    */
   abstract void addCondiments();
   /**
    * 浸泡
    */
   void soak() {
      System.out.println("第三步:黄豆和配料开始浸泡, 需要3小时 ");
   }
   /**
    * 打碎
    */
   void beat() {
      System.out.println("第四步:黄豆和配料放到豆浆机去打碎  ");
   }
   /**
    * 钩子方法,决定是否需要添加配料
    * @return
    */
   boolean customerWantCondiments() {
      return true;
   }
}

【纯豆浆】

package com.atguigu.template.improve;
/**
 * 纯豆浆
 */
public class PureSoyaMilk extends SoyaMilk{
   @Override
   void addCondiments() {
      //空实现
   }
   /**
    * 如果需要自定义钩子函数,就重写这个方法,不需要就不用重写
    * @return
    */
   @Override
   boolean customerWantCondiments() {
      return false;
   }
}

【主类】

package com.atguigu.template.improve;
public class Client {
   public static void main(String[] args) {
      System.out.println("----制作纯豆浆----");
      SoyaMilk pureSoyaMilk = new PureSoyaMilk();
      pureSoyaMilk.make();
   }
}

【运行】

----制作纯豆浆----
第一步:选择好的新鲜黄豆  
第三步:黄豆和配料开始浸泡, 需要3小时 
第四步:黄豆和配料放到豆浆机去打碎  
Process finished with exit code 0

案例二

实现

【抽象类】

package com.atguigu.template.Sample;
/**
 * 抽象类AbstractDisplay
 */
public abstract class AbstractDisplay {
    /**
     * 交给子类去实现的抽象方法(1) open
     */
    public abstract void open();
    /**
     * 交给子类去实现的抽象方法(2) print
     */
    public abstract void print();
    /**
     * 交给子类去实现的抽象方法(3) close
     */
    public abstract void close();
    /**
     * 本抽象类中实现的display方法,模板方法
     */
    public final void display() {
        // 首先打开…
        open();
        // 循环调用5次print
        for (int i = 0; i < 5; i++) {
            print();
        }
        // …最后关闭。这就是display方法所实现的功能
        close();
    }
}

【子类:CharDisplay】

package com.atguigu.template.Sample;
/**
 * CharDisplay是AbstractDisplay的子类
 */
public class CharDisplay extends AbstractDisplay {
    /**
     * 需要显示的字符
     */
    private char ch;
    public CharDisplay(char ch) {
        // 保存在字段中
        this.ch = ch;
    }
    public void open() {
        // 显示开始字符"<<"
        System.out.print("<<");
    }
    public void print() {
        // 显示保存在字段ch中的字符
        System.out.print(ch);
    }
    public void close() {
        // 显示结束字符">>"
        System.out.println(">>");
    }
}

【子类:StringDisplay】

package com.atguigu.template.Sample;
/**
 * StringDisplay也是AbstractDisplay的子类
 */
public class StringDisplay extends AbstractDisplay {
    /**
     * 需要显示的字符串
     */
    private String string;
    /**
     * 以字节为单位计算出的字符串长度
     */
    private int width;
    public StringDisplay(String string) {
        this.string = string;
        // 将字符串的字节长度也保存在字段中,以供后面使用
        this.width = string.getBytes().length;
    }
    public void open() {
        // 调用该类的printLine方法画线
        printLine();
    }
    public void print() {
        // 给保存在字段中的字符串前后分别加上"|"并显示出来
        System.out.println("|" + string + "|");
    }
    public void close() {
        // 与open方法一样,调用printLine方法画线
        printLine();
    }
    /**
     * 被open和close方法调用。由于可见性是private,因此只能在本类中被调用
     */
    private void printLine() {
        // 显示表示方框的角的"+"
        System.out.print("+");                
        for (int i = 0; i < width; i++) {
            // 显示width个"-",和"+"一起组成边框
            System.out.print("-");                       
        }
        // 显示表示方框的角的"+"
        System.out.println("+");                 
    }
}

【主类】

package com.atguigu.template.Sample;
public class Main {
    public static void main(String[] args) {
        // 生成一个持有'H'的CharDisplay类的实例 
        AbstractDisplay d1 = new CharDisplay('H');
        // 生成一个持有"Hello, world."的StringDisplay类的实例 
        AbstractDisplay d2 = new StringDisplay("Hello, world.");
        // 生成一个持有"你好,世界。"的StringDisplay类的实例 
        AbstractDisplay d3 = new StringDisplay("你好,世界。");
        /*
         * 由于d1、d2和d3都是AbstractDisplay类的子类
         * 可以调用继承的display方法
         * 实际的程序行为取决于CharDisplay类和StringDisplay类的具体实现
         */
        d1.display();
        d2.display();
        d3.display();
    }
}

【运行】

<<HHHHH>>
+-------------+
|Hello, world.|
|Hello, world.|
|Hello, world.|
|Hello, world.|
|Hello, world.|
+-------------+
+------------------+
|你好,世界。|
|你好,世界。|
|你好,世界。|
|你好,世界。|
|你好,世界。|
+------------------+
Process finished with exit code 0

模板方法模式在IOC的源码分析

实现类有一个模板方法

另一个钩子方法

除此之外,java.io.InputStream类中也使用了Template Method

总结

【优点】

  • 既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现
  • 实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用。如果模板方法有bug只需要修改一个类即可,不使用模板方法模式的话,就需要修改多个类的代码
  • 父类和子类具有一致性:在示例程序中,不论是CharDisplay的实例还是StringDisplay的实例,都是先保存在AbstractDisplay类型的变量中,然后再来调用display方法的。使用父类类型的变量保存子类实例的优点是,即使没有用instanceof等指定子类的种类程序也能正常工作。无论在父类类型的变量中保存哪个子类的实例,程序都可以正常工作,这种原则称为里氏替换原则

【不足】

  • 不足:每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大
  • 一般模板方法都加上final关键字, 防止子类重写模板方法

思考

思考一

问:如果想要让示例程序中的open、print、close方法可以被具有继承关系的类和同一程序包中的类调用,但是不能被无关的其他类调用,应当怎么做呢?

答:使用protected关键字修饰这些方法,不要使用public。

【Java四种权限修饰符】

public protected (default) private
类本身
同一个包下的类 ×
不同包,但是我的子类 × ×
不同包非子类 × × ×

注:default不需要写出来,不写权限修饰符默认就是defaunt

思考二

问:Java中的接口与抽象类很相似。接口同样也是抽象方法的集合,但是在模板方法模式中,我们却无法使用接口来扮演AbstractClass 角色,请问这是为什么呢?

答:因为使用接口无法实现模板方法和其他的方法。

文章说明

  • 本文章为本人学习尚硅谷的学习笔记,文章中大部分内容来源于尚硅谷视频(点击学习尚硅谷相关课程),也有部分内容来自于自己的思考,发布文章是想帮助其他学习的人更方便地整理自己的笔记或者直接通过文章学习相关知识,如有侵权请联系删除,最后对尚硅谷的优质课程表示感谢。
  • 本人还同步阅读《图解设计模式》书籍(图解设计模式/(日)结城浩著;杨文轩译–北京:人民邮电出版社,2017.1),进而综合两者的内容,让知识点更加全面
目录
相关文章
|
17天前
|
设计模式 消息中间件 搜索推荐
Java 设计模式——观察者模式:从优衣库不使用新疆棉事件看系统的动态响应
【11月更文挑战第17天】观察者模式是一种行为设计模式,定义了一对多的依赖关系,使多个观察者对象能直接监听并响应某一主题对象的状态变化。本文介绍了观察者模式的基本概念、商业系统中的应用实例,如优衣库事件中各相关方的动态响应,以及模式的优势和实际系统设计中的应用建议,包括事件驱动架构和消息队列的使用。
|
28天前
|
设计模式 Java 数据库连接
Java编程中的设计模式:单例模式的深度剖析
【10月更文挑战第41天】本文深入探讨了Java中广泛使用的单例设计模式,旨在通过简明扼要的语言和实际示例,帮助读者理解其核心原理和应用。文章将介绍单例模式的重要性、实现方式以及在实际应用中如何优雅地处理多线程问题。
32 4
|
1月前
|
设计模式 JavaScript Java
Java设计模式:建造者模式详解
建造者模式是一种创建型设计模式,通过将复杂对象的构建过程与表示分离,使得相同的构建过程可以创建不同的表示。本文详细介绍了建造者模式的原理、背景、应用场景及实际Demo,帮助读者更好地理解和应用这一模式。
|
3天前
|
安全 Java API
java如何请求接口然后终止某个线程
通过本文的介绍,您应该能够理解如何在Java中请求接口并根据返回结果终止某个线程。合理使用标志位或 `interrupt`方法可以确保线程的安全终止,而处理好网络请求中的各种异常情况,可以提高程序的稳定性和可靠性。
26 6
|
18天前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
|
16天前
|
存储 监控 小程序
Java中的线程池优化实践####
本文深入探讨了Java中线程池的工作原理,分析了常见的线程池类型及其适用场景,并通过实际案例展示了如何根据应用需求进行线程池的优化配置。文章首先介绍了线程池的基本概念和核心参数,随后详细阐述了几种常见的线程池实现(如FixedThreadPool、CachedThreadPool、ScheduledThreadPool等)的特点及使用场景。接着,通过一个电商系统订单处理的实际案例,分析了线程池参数设置不当导致的性能问题,并提出了相应的优化策略。最终,总结了线程池优化的最佳实践,旨在帮助开发者更好地利用Java线程池提升应用性能和稳定性。 ####
|
18天前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
11天前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
11天前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
32 3
|
12天前
|
监控 Java 开发者
深入理解Java中的线程池实现原理及其性能优化####
本文旨在揭示Java中线程池的核心工作机制,通过剖析其背后的设计思想与实现细节,为读者提供一份详尽的线程池性能优化指南。不同于传统的技术教程,本文将采用一种互动式探索的方式,带领大家从理论到实践,逐步揭开线程池高效管理线程资源的奥秘。无论你是Java并发编程的初学者,还是寻求性能调优技巧的资深开发者,都能在本文中找到有价值的内容。 ####