设计模式浅析(九) ·模板方法模式

news2025/2/28 12:09:43

设计模式浅析(九) ·模板方法模式

日常叨逼叨

java设计模式浅析,如果觉得对你有帮助,记得一键三连,谢谢各位观众老爷😁😁


模板方法模式

概念

模板方法模式(Template Method Pattern)在Java中是一种非常实用的设计模式,它允许我们在一个方法中定义一个算法的骨架,同时允许子类在不改变算法结构的情况下重新定义某些步骤的具体内容。这种模式非常适合于那些算法的整体步骤固定,但某些步骤的具体实现可能因需求不同而有所变化的场景。

组成
  1. 抽象类(Abstract Class)
    • 定义了一个或多个抽象方法,这些抽象方法由具体子类来实现。
    • 包含一个模板方法,这个方法定义了算法的骨架,并调用了在抽象类中定义的抽象方法。
    • 模板方法通常被声明为final,以防止子类覆盖它。
    • 模板方法通常还包含一些普通方法,这些方法可以被具体子类直接使用或覆盖。
  2. 具体子类(Concrete Subclasses)
    • 实现了抽象类中定义的抽象方法,提供了这些抽象方法的具体实现。
    • 通过继承抽象类,子类可以重写普通方法,以提供不同的实现。
    • 子类可以通过调用父类的模板方法来执行算法。
  3. 客户端
    • 客户端代码创建具体类的实例,并调用模板方法来执行算法。

案例

继续以一家饮品店为例子:

A饮品店有咖啡(coffee)和茶(tea)两种饮品,对于其制作方法如下:

  • 咖啡冲泡法

    • (1)把水煮沸

    • (2)用沸水冲泡咖啡

    • (3)把咖啡倒进杯子

    • (4)加糖和牛奶

  • 茶水冲泡法

    • (1)把水煮沸

    • (2)用沸水浸泡茶叶

    • (3)把茶倒进杯子

    • (4)加柠檬

让我们扮演冲泡师傅,快速的创建茶水和咖啡

//coffee
public class Coffee {
    void prepareRecipe() {
        boilWater();
        brewCoffeeGrinds();
        pourInCup();
        addSugarAndMilk();
    }

    public void boilWater() {
        System.out.println("step 1 Boiling water");
    }

    public void brewCoffeeGrinds() {
        System.out.println("step 2 Dripping Coffee through filter");
    }

    public void pourInCup() {
        System.out.println("step 3 Pouring into cup");
    }

    public void addSugarAndMilk() {
        System.out.println("step 4 Adding Sugar and Milk");
    }
}
//Tea
public class Tea {
    void prepareRecipe() {
        boilwater();
        steepTeaBag();
        pourInCup();
        addLemon();
    }

    public void boilwater() {
        System.out.println("step 1 Boiling water");
    }

    public void steepTeaBag() {
        System.out.println("step 2 Steeping the tea");
    }

    public void addLemon() {
        System.out.println("step 3 Adding Lemon");
    }

    public void pourInCup() {
        System.out.println("step 4 Pouring into cup");
    }
}

仔细观察上述的代码,会发现coffee和tea 除了第2步骤和第4步骤不一样外,其他的步骤几乎一致,代码存在者较高的冗余。是不是可以将一些代码抽取出来?

那么第一次抽取之后,类图可能会变成这个样子

在这里插入图片描述

但是仔细想一想上面的制作方法

在这里插入图片描述

让我们来思考这一点:浸泡(steep)和冲泡(brew)差异其实不大。所以我们给它一个新的方法名称,比方说brew(),然后不管是泡茶或冲泡咖啡我们都用这个名称。

类似地,加糖和牛奶也和加柠檬很相似:都是在饮料中加入调料,由此,我们再做一次公共的提取。

首先,我们定义一个抽象类DrinksMake,在其中定义一个final的方法,规定具体的步骤:

abstract class DrinksMake {
    //模板方法
    public final void makeDrinks() {

        //step 1 烧水
        boiledWater();
        //step 2 冲泡
        brew();
        //step3 倒出到杯子中
        pourInCup();
        //step4 添加配料
        addTheIngredients();

    }


    protected abstract void addTheIngredients();

    protected abstract void brew();

    protected void boiledWater() {
        System.out.println("step1:boiled water!");
    }

    protected void pourInCup() {
        System.out.println("step3: pourInCup !");
    }


}

然后,我们定义制作咖啡的具体实现以及茶水的具体实现

public class CoffeeMake extends DrinksMake {

    @Override
    protected void addTheIngredients() {
        System.out.println("step 4 : add Sugar and Milk into Coffee");
    }

    @Override
    protected void brew() {
        System.out.println("step 2 : brew Coffee");
    }
}
public class TeaMake extends DrinksMake {


    @Override
    protected void addTheIngredients() {
        System.out.println("step 4 : add Lemon into tea");
    }

    @Override
    protected void brew() {
        System.out.println("step 2 : brew tea");
    }
}

那么对于客户端来说,我们分别制作一杯咖啡和一杯茶

public class Client {
    public static void main(String[] args) {
        TeaMake teaMake = new TeaMake();
        CoffeeMake coffeeMake = new CoffeeMake();

        teaMake.makeDrinks();
        coffeeMake.makeDrinks();
    }
}

运行结果:

step1:boiled water!
step 2 : brew tea
step3: pourInCup !
step 4 : add Lemon into tea
step1:boiled water!
step 2 : brew Coffee
step3: pourInCup !
step 4 : add Sugar and Milk into Coffee

Process finished with exit code 0

至此我们实现了设计模式中的模板方法模式

模板方法模式中的钩子hook

我们在上述的抽象父类中再加入一个方法

void hook(){}
//但是这个方法暂时什么都不做

我们也可以有默认不做事的方法,我们称这神方法为hook(钩子)。子类可以视情况决定要不要覆盖它们。接下来就会知道钩子的实际用途。

试想一下,如果在step4,添加小料的时候,是不是应该询问一下顾客的意见?不能问不问就为顾客添加一些小料,这个是不太合理的,那么我们的代码可以这么修改:

abstract class DrinksMake {
    //模板方法
    public final void makeDrinks() {

        //step 1 烧水
        boiledWater();
        //step 2 冲泡
        brew();
        //step3 倒出到杯子中
        pourInCup();
        //step4 添加配料
        if (ifNeedIngredients()) {
            addTheIngredients();
        }

    }


    protected abstract void addTheIngredients();

    protected abstract void brew();

    protected void boiledWater() {
        System.out.println("step1:boiled water!");
    }

    protected void pourInCup() {
        System.out.println("step3: pourInCup !");
    }


    /***
     * 钩子方法,子类可以选择重写或者不重写
     */
    boolean ifNeedIngredients() {
        return true;
    }
    
}

创建一个类似于钩子的方法,默认返回是添加小料,可以在子类中进行重写进行相关的操作,我们可以在子类中进行询问顾客是否需要小料

public class CoffeeMakeWithHook extends DrinksMake {

    @Override
    protected void addTheIngredients() {
        System.out.println("step 4 : add Sugar and Milk into Coffee");
    }

    @Override
    protected void brew() {
        System.out.println("step 2 : brew Coffee");
    }

    @Override
    boolean ifNeedIngredients() {
        String answer = getUserInput();
        if (answer.toLowerCase().startsWith("y")) {
            return true;
        } else
            return false;
    }

    //  让用户输入他们对调料的决定。根据用户的输入返回tue或false
    private String getUserInput() {
        String answer = null;
        System.out.print("would you like milk and sugar with your coffee (y/n) ?");
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        try {
            answer = in.readLine();
        } catch (IOException e) {
            System.err.println("IO error trying to read your answer");
        }
        if (answer == null) {
            return "no";
        }
        return answer;
    }
}

再次创建客户端代码,进行coffeewithhook的调用

public class Client {
    public static void main(String[] args) {
        CoffeeMakeWithHook coffeeMake = new CoffeeMakeWithHook();
        coffeeMake.makeDrinks();

    }
}

运行结果如下:

不需要小料:

step1:boiled water!
step 2 : brew Coffee
step3: pourInCup !
would you like milk and sugar with your coffee (y/n) ?n

Process finished with exit code 0

需要小料:

step1:boiled water!
step 2 : brew Coffee
step3: pourInCup !
would you like milk and sugar with your coffee (y/n) ?y
step 4 : add Sugar and Milk into Coffee

Process finished with exit code 0

优缺点

优点:
  1. 代码复用:模板方法模式允许你定义一个算法的骨架,而将一些步骤延迟到子类中。这允许子类在不改变算法结构的情况下重定义某些步骤的具体内容,从而实现了代码的复用。
  2. 扩展性:由于模板方法模式将算法中的固定部分与可变部分分离,这使得子类可以通过扩展来增加新的行为,从而提高了代码的扩展性。
  3. 封装性:通过将不变的部分封装在父类中,模板方法模式隐藏了实现细节,只暴露必要的接口给子类。这有助于减少子类的耦合度。
  4. 行为控制:模板方法模式允许通过子类覆盖父类的钩子方法来决定某一特定步骤是否需要执行,从而提供了一种反向控制结构。
缺点:
  1. 代码可读性:由于模板方法模式中的钩子方法和抽象方法的调用顺序可能比较复杂,这可能会增加代码的阅读难度,尤其是对于新手来说。
  2. 继承的缺点:模板方法模式依赖于继承来实现代码复用,而继承本身有一些固有的缺点,如破坏封装性、子类与父类之间的强耦合等。
  3. 扩展限制:如果父类中的模板方法被声明为final,那么子类将无法覆盖该方法,这可能会限制子类的扩展性。

总的来说,Java模板方法模式在需要定义一系列复杂算法时非常有用,它能够有效地实现代码复用和扩展。然而,它也有一些缺点需要注意,如代码可读性和继承的局限性等。因此,在使用模板方法模式时,需要根据具体情况权衡其优缺点。


代码相关代码可以参考 代码仓库🌐

ps:本文原创,转载请注明出处


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

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

相关文章

【MATLAB】VMD_ MFE_SVM_LSTM 神经网络时序预测算法

有意向获取代码,请转文末观看代码获取方式~也可转原文链接获取~ 1 基本定义 VMD_MFE_SVM_LSTM神经网络时序预测算法是一种结合了变分模态分解(VMD)、多尺度特征提取(MFE)、支持向量机(SVM)和长…

Java+SpringBoot+Vue+MySQL:美食推荐系统的技术革新

✍✍计算机编程指导师 ⭐⭐个人介绍:自己非常喜欢研究技术问题!专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目:有源码或者技术上的问题欢迎在评论区一起讨论交流! ⚡⚡ Java实战 |…

启动spark-shell时报错java.lang.NumberFormatException: For input string: “0x100“

一、问题描述 安装完Spark后,启动spark shell时报错 java.lang.NumberFormatException: For input string: "0x100" 如下图: 二、解决办法 1.更换scala的版本 2.更改环境变量 使用vim编辑器打开用户的环境变量配置文件 vim ~/.bashrc s…

解锁财务信任,掌握企业业务合作中的倾听艺术

企业在经营管理过程中,经常会思考如何才能成为一个完美的财务业务融合体,实现业务合作的最大价值。当我们置身于企业战略规划的构建过程中,就会明显的感觉到,获得财务信任有助于指导团队做出重大决策并推动企业未来的行动。市场和…

【蓝桥杯单片机入门记录】动态数码管

目录 一、数码管动态显示概述 二、动态数码管原理图 (1)原理图 (2)动态数码管如何与芯片相连 (3)“此器件” ——>锁存器74HC573 三、动态数码管显示例程 (1)例程1&#xf…

Day02:Web架构前后端分离站Docker容器站集成软件站建站分配

目录 常规化站点部署 站库分离 前后端分离 集成软件搭建Web应用 Docker容器搭建Web应用 建立分配站 静态 与 伪静态 总结 章节知识点: 应用架构:Web/APP/云应用/三方服务/负载均衡等 安全产品:CDN/WAF/IDS/IPS/蜜罐/防火墙/杀毒等 渗…

react useRef用法

1&#xff0c;保存变量永远不丢失 import React, { useState,useRef } from react export default function App() { const [count,setcount] useState(0) var mycount useRef(0)//保存变量永远不丢失--useRef用的是闭包原理 return( <div> <button onClick{()>…

Linux笔记--硬链接与软链接

一、硬链接 1.inode和block 文件包含两部分数据&#xff1a;文件属性和实际内容&#xff0c;属性放在inode中&#xff0c;实际内容放在data block中。还有个超级区块&#xff08;superblock&#xff09;记录整个文件系统的整体信息&#xff0c;包括inode和block的总量&#x…

%00截断 [GKCTF 2020]cve版签到

打开题目 F12之后在Headers中发现hint 两者结合利用零字符截断使get_headers()请求到本地127.0.0. 结合链接 构造 ?urlhttp://127.0.0.1%00www.ctfhub.com 必须以123结尾 ?urlhttp://127.0.0.123%00www.ctfhub.com 得到flag 知识点&#xff1a; PHP中get_headers函数 g…

合并spark structured streaming处理流式数据产生的小文件

备注&#xff1a; By 远方时光原创&#xff0c;可转载&#xff0c;不能复制到其他平台 背景&#xff1a;做流批一体&#xff0c;湖仓一体的大数据架构&#xff0c;常见的做法就是 数据源->spark Streaming->ODS&#xff08;数据湖&#xff09;->spark streaming->…

如何让网页APP化 渐进式Web应用(PWA)

前言 大家上网应该发现有的网页说可以安装对应应用&#xff0c;结果这个应用好像就是个web&#xff0c;不像是应用&#xff0c;因为这里采用了PWA相关技术。 PWA&#xff0c;全称为渐进式Web应用&#xff08;Progressive Web Apps&#xff09;&#xff0c;是一种可以提供类似…

无法访问云服务器上部署的Docker容器(二)

说明&#xff1a;记录一次使用公网IP 接口地址无法访问阿里云服务接口的问题&#xff1b; 描述 最近&#xff0c;我使用Docker部署了jeecg-boot项目&#xff0c;部署过程都没有问题&#xff0c;也没有错误信息。部署完成后&#xff0c;通过下面的地址访问后端Swagger接口文档…

Facebook的虚拟社交愿景:元宇宙时代的新起点

在当今数字化时代&#xff0c;社交媒体已经成为人们生活中不可或缺的一部分。而随着科技的不断进步和社会的发展&#xff0c;元宇宙已经成为了人们关注的热点话题之一。作为社交媒体的领军企业之一&#xff0c;Facebook也在积极探索虚拟社交的未来&#xff0c;将其视为元宇宙时…

微服务-微服务链路追踪组件Skywalking实战

自动化监控系统Prometheus&Grafana实战&#xff1a; 4 trem APM-性能监控项目班&#xff1a; https://vip.tulingxueyuan.cn/detail/p_602e574ae4b035d3cdb8f8fe/6 1. skywalking是什么 1.1 Skywalking主要功能特性 1.2 Skywalking整体架构 1.3 SkyWalking 环境搭建部…

【Linux C | 网络编程】套接字选项、getsockopt、setsockopt详解及C语言例子

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

Prompt 编程的优化技巧

一、为什么要优化 一&#xff09;上下文限制 目前 GPT-3.5 以及 GPT-4最大支持 16K 上下文&#xff0c;比如你输入超过 16k 的长文本&#xff0c;ChatGPT 会提示文本过大&#xff0c;为了避免 GPT 无法回复&#xff0c;需要限制 上下文在16k 以内 上下文对于 GPT 来说是非常重…

【Java代码审计】SSRF

什么是SSRF&#xff1f; SSRF(Server-Side Request Forge, 服务端请求伪造) &#xff0c;即攻击者构造恶意参数使服务端对其它内/外网系统进行访问或者攻击的一种方式。 Java支持的网络请求协议&#xff1a; file ftp http https gopher(jdk≤1.7) jar netdoc mailto SSRF代码…

接口的定义与实现方法

作用&#xff1a; 约束 定义一些方法&#xff0c;让不同的人实现 public abstract piblic static final 接口不能被实例化&#xff0c;接口中没有构造方法 implements可以实现多个接口 必须要重写接口中的方法~

Vue前端对请假模块——请假开始时间和请假结束时间的校验处理

开发背景&#xff1a;Vueelement组件开发 业务需求&#xff1a;用户提交请假申请单&#xff0c;请假申请的业务逻辑处理 实现&#xff1a;用户选择开始时间需要大于本地时间&#xff0c;不得大于请假结束时间&#xff0c;请假时长根据每日工作时间实现累加计算 页面布局 在前…

Mistral发布语言大模型Mistral Large;法国新星Mistral挑战 OpenAI 霸主地位

&#x1f989; AI新闻 &#x1f680; Mistral发布语言大模型Mistral Large 摘要&#xff1a;Mistral Large 是 Mistral AI 公司最新发布的旗舰语言模型&#xff0c;具备顶尖水平的推理能力。它主要被设计用于处理复杂的多语言推理任务&#xff0c;比如文本理解、转换和代码生…