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

news2024/9/24 19:16:14

介绍

基本介绍

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

使用说明

在这里插入图片描述

【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四种权限修饰符】

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

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

思考二

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

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

文章说明

  • 本文章为本人学习尚硅谷的学习笔记,文章中大部分内容来源于尚硅谷视频(点击学习尚硅谷相关课程),也有部分内容来自于自己的思考,发布文章是想帮助其他学习的人更方便地整理自己的笔记或者直接通过文章学习相关知识,如有侵权请联系删除,最后对尚硅谷的优质课程表示感谢。
  • 本人还同步阅读《图解设计模式》书籍(图解设计模式/(日)结城浩著;杨文轩译–北京:人民邮电出版社,2017.1),进而综合两者的内容,让知识点更加全面

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/822843.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue引入

1. vue引入 第一种方法&#xff1a;在线引入 <script src"https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> 第二种方法&#xff1a;本地引入 2. 语法学习 el用于绑定id&#xff0c;data用于定义数据如下例题 <!DOCTYPE html> <html…

xinput1_4.dll丢失的解决方法,三种解决方法分享

xinput1_4.dll是一个动态链接库文件&#xff08;DLL&#xff09;&#xff0c;它是Microsoft DirectX的一部分&#xff0c;用于处理游戏控制器输入。当你的电脑提示xinput1_4.dll文件丢失时&#xff0c;意味着与这个文件相关的游戏或应用程序无法正常运行。 当你的电脑提示xinp…

正负样本分配策略(OTA, SimOTA,TAS)

文章目录 OTASimOTATALATSS OTA 论文&#xff1a;《OTA: Optimal Transport Assignment for Object Detection》 代码&#xff1a;Megvii-BaseDetection/OTA 标签分配算法 目标CNN-based的目标检测器是预测 pre-defined anchors 的类别 (cls) 以及偏移量 (reg) 。 为了训练目标…

go 结构体 - 值类型、引用类型 - 结构体转json类型 - 指针类型的种类 - 结构体方法 - 继承 - 多态(interface接口) - 练习

目录 一、结构体 1、python 与 go面向对象的实现&#xff1a; 2、初用GO中的结构体&#xff1a;&#xff08;实例化一个值类型的数据&#xff08;结构体&#xff09;&#xff09; 输出结果不同的三种方式 3、实例化一个引用类型的数据&#xff08;结构体&#xff09; 4、…

esp32 wifi无线透传

wifi无线透传 目录 wifi无线透传[TOC](目录) 一、Esp32代码1.1 下载烧写 二、星空内网穿透配置2.1 平台注册2.2 充值2元(用于实名认证)2.3 实名认证2.4 创建隧道2.5 下载软件2.6 配置文件2.7 启动服务 因为常见的无线传输视频或图片&#xff0c;只能在局域网内中实现&#xff0…

【并发专题】手写MyReentantLock

分析 ReentantLock的特点如下&#xff1a; 首先是继承自AQS的可中断可以设置超时时间可以切换公平锁/非公平锁支持多个条件变量支持可重入 事实上&#xff0c;上面的很多东西AQS已经帮忙实现了&#xff0c;所以想要复刻一个不是很难。仔细观察一下源码&#xff0c;我们需要重…

Text-to-SQL小白入门(一)

摘要 本文主要介绍了Text-to-SQL研究的定义、意义、研究方法以及未来展望&#xff0c;主要是对Text-to-SQL领域进行一个初步的认识和了解&#xff0c;适合初学者入门了解。 1 引言 作为Text-to-SQL领域的小白&#xff0c;学习该领域的最好方式就是看最新的综述文章&#xff…

Junit4入门之什么是单元测试?

干了一年多的后端了&#xff0c;从来没有了解过单元测试。虽然我知道测试不仅仅是测试们的任务&#xff0c;后端也要进行自测来保证自己的代码的可用性&#xff0c;但我一直都只是用postman来实施的&#xff0c;调用调通了即可。虽然我也知道Junit是用于测试的软件&#xff0c;…

几种常用到的 Hybrid App 框架方案

移动操作系统在经历了诸神混战之后&#xff0c;BlackBerry OS、Symbian OS、Windows Phone等早期的移动操作系统逐渐因失去竞争力而退出。目前&#xff0c;市场上主要只剩下安卓和iOS两大阵营&#xff0c;使得iOS和安卓工程师成为抢手资源。然而&#xff0c;由于两者系统的差异…

学了python的心得体会200字,学python心得体会1000字

大家好&#xff0c;本文将围绕学了python的心得体会200字展开说明&#xff0c;学python心得体会1000字是一个很多人都想弄明白的事情&#xff0c;想搞清楚学python心得体会800字需要先了解以下几个事情。 一、个人学期总结 本学期在missdu的带领下&#xff0c;进行了python的学…

Jenkins通过OpenSSH发布WinServer2016

上一篇文章> Jenkins集成SonarQube代码质量检测 一、实验环境 jenkins环境 jenkins入门与安装 容器为docker 主机IP系统版本jenkins10.10.10.10rhel7.5 二、OpenSSH安装 1、下载 官网地址&#xff1a;https://learn.microsoft.com/zh-cn/windows-server/administration/op…

Spring Boot的创建和运行

目录 1.Spring Boot的优点 2.Spring Boot项目创建 2.1使用Idea创建 2.2网页版创建 3.项目目录介绍和运行 3.1运行项目 3.2输出 4.注意事项 4.1正确路径 4.2小结&#xff1a;约定大于配置 1.Spring Boot的优点 ●快速集成框架&#xff0c;Spring Boot 提供了启动添…

解决:Uncaught (in promise) SyntaxError: “[object Object]“ is not valid JSON 问题的过程

1、问题描述&#xff1a; 其一、报错为&#xff1a; Uncaught (in promise) SyntaxError: "[object Object]" is not valid JSON 中文为&#xff1a; 未捕获&#xff08;承诺中&#xff09;语法错误&#xff1a;“[object Object]”不是有效的 JSON 其二、问题描…

阿丹千问vue页面升级-使用Markdown形式展示回答--markdown-it库

阿丹&#xff1a; 在之前开发的阿丹千问 发现回复的文章格式使用 Markdown的格式。所以想使用Markdown的方式来给页面来个升级。 下面就是升级以及开发的过程。 升级思路 使用vue中的markdown-it库 在Vue页面中使用Markdown文档 安装markdown-it&#xff1a; 在Vue项目中…

《HeadFirst设计模式(第二版)》第一章源码

代码文件目录结构&#xff1a; FlyBehavior.java package Chapter1_StrategyPattern.ch1_3_behavior.behaviors.fly;public interface FlyBehavior {void fly(); } FlyNoWay.java package Chapter1_StrategyPattern.ch1_3_behavior.behaviors.fly;public class FlyNoWay imp…

使用Jetpack Compose构建时间轴组件的逐步指南

使用Jetpack Compose构建时间轴组件的逐步指南 最近&#xff0c;我们开发一个时间轴组件&#xff0c;显示用户与客户之间的对话。每个对话节点应具有自己的颜色&#xff0c;取决于消息的状态&#xff0c;并且连接消息的线条形成颜色之间的渐变过渡。 我们慷慨地估计了未来的工…

C++学习day--18 空指针和函数指针、引用

1、void 类型指针 void > 空类型 void* > 空类型指针&#xff0c; 只存储地址的值&#xff0c;丢失类型&#xff0c;无法访问&#xff0c;要访问其值&#xff0c;我们必须对这个指 针做出正确的类型转换&#xff0c;然后再间接引用指针 。 所有其它类型的指针都可以隐…

基于C语言 --- 自己写一个扫雷小游戏

C语言程序设计笔记---020 初阶扫雷小游戏(开源)1、arr_main2.c程序大纲2、arr_game2.h3、arr_game2.c3.1、 自定义初化函数 InitBoard( ) 和 自定义显示函数 DisPlayBoard( )3.2、 自定义布置雷函数 SetMine( )3.4、 自定义排查雷函数 FindMine( ) 4、结束语 初阶扫雷小游戏(开…

Redis安装部署(基于windows平台)

redis简介 键值对存储数据库是NoSQL数据库的一种类型&#xff0c;也是最简单的NoSQL数据库。顾名思义&#xff0c;键值对存储数据库中的数据是以键值对的形式来存储的。常见的键值对存储数据库有Redis、Tokyo Cabinet/Tyrant、Voldemort以及Oracle BDB数据库。 Remote Diction…