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

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

日常叨逼叨

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:本文原创,转载请注明出处


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

探针台有哪些优势

探针台广泛应用于半导体、光电以及集成电路等行业,用于测试、调试和维修电子设备、电路板和芯片。 探针台具有以下优势: 1.能够确保相关产品研发的质量。 2.能够有效缩短研发时间和资金成本。 3.可以缩减器件的制作工艺成本。 4.具有更加可靠和省时…

开源大数据集群部署(十二)Ranger 集成 hive

作者:櫰木 1、解压安装 在hd1.dtstack.com主机上执行(一般选择hiveserver2节点) 解压ranger-2.3.0-hive-plugin.tar.gz [roothd1.dtstack.com software]#tar -zxvf ranger-2.3.0-hive-plugin.tar.gz修改install.properties配置 [roothd1…

代码随想录Leetcode474. 一和零

题目&#xff1a; 代码(首刷看解析 2024年2月26日&#xff09; class Solution { public:// 二维 0 1背包int findMaxForm(vector<string>& strs, int m, int n) {// 1 二维 [i]表示 0 的个数&#xff0c;上限m; [j]表示 1 的个数&#xff0c;上限nvector<vector…

QYYB-01 无线雨量报警仪 集监测 采集 记录 报警一体机

产品概述 无线雨量报警仪&#xff0c;又名简易雨量报警器、遥测雨量报警仪&#xff0c;是一款可以实时测量并显示降雨量的仪器&#xff0c;支持无线传输数据、同步显示新数据、数据超标自动报警、自动存储数据、查询和导出数据等多项功能&#xff0c;由翻斗式雨量传感器、数据采…

【ArcGIS】基于DEM/LUCC等数据统计得到各集水区流域特征

基于DEM/LUCC等数据统计得到各集水区流域特征 提取不同集水区各类土地利用类型比例步骤1&#xff1a;划分集水区为独立面单元步骤2&#xff1a;批量掩膜提取得到各集水区土地利用类型比例步骤3&#xff1a;导入各集水区LUCC数据并统计得到各类型占比 提取坡度特征流域面坡度河道…

安防视频监控平台EasyNVR级联视频上云管理平台EasyNVS,出现报错“i/o deadline reached”该如何解决?

上云网关管理平台EasyNVS视频综合管理系统具备汇聚与管理EasyGBS、EasyNVR等平台的能力&#xff0c;系统可以将接入的视频资源实现视频能力统一输出&#xff0c;并能进行远程可视化运维等管理功能&#xff0c;还能解决设备现场没有固定公网IP却需要在公网直播的需求。 有用户反…

11个Linux性能分析命令

Linux性能分析命令有很多&#xff0c;不同的命令可以用来监控不同的系统资源和活动。根据您的问题&#xff0c;我为您推荐以下11个常用的Linux性能分析命令&#xff1a; uptime&#xff1a;显示系统的运行时间和平均负载。dmesg&#xff1a;显示系统的启动信息和内核的日志信息…

ADC制剂生产过程中的微粒控制-隧道烘箱在线粒子监测系统 中邦兴业

ADC制剂生产过程中的污染和交叉污染控制需要从多个方面入手&#xff0c;包括生产环境、设备、原辅料、生产过程、人员卫生和培训以及微生物监控等。只有全面、有效地实施这些控制措施&#xff0c;才能确保ADC制剂的质量和安全性。 ADC制剂生产过程中的微粒控制 ADC制剂生产中的…

4 种策略让 MySQL 和 Redis 数据保持一致

先阐明一下 MySQL 和 Redis 的关系&#xff1a;MySQL 是数据库&#xff0c;用来持久化数据&#xff0c;一定程度上保证数据的可靠性&#xff1b;Redis 是用来当缓存&#xff0c;用来提升数据访问的性能。 关于如何保证 MySQL 和 Redis 中的数据一致&#xff08;即缓存一致性问…

备战蓝桥杯---树形DP基础1

我们先来看几个比较简单的例子来引入&#xff1a; 我们令f[i]表示以i为根节点的子树大小&#xff0c;易得状态转移方程为&#xff1a; f[i]1f[son1]....f[soni]; 我们用DFS即可&#xff0c;下面是大致的模板&#xff1a; 让我们来看看几道题吧&#xff1a; 1.贪心树形DPDFS&…

基于java springmvc+mybatis学生考试系统设计和实现

基于java springmvcmybatis学生考试系统设计和实现 博主介绍&#xff1a;多年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 ⭐留言 文末获取…

Unity2023.1.19_Embedded Browser-ZFBrowser插件

Unity2023.1.19_Embedded Browser-ZFBrowser插件 官方说明文档可以仔细看一下&#xff1a; ZFBrowser Documentation (zenfulcrum.com) ZFBrowser插件的简单直接使用&#xff1a; 导入插件包资源&#xff0c;遵循常规导包原则即可&#xff1b; 抓取包文件夹下的预制体组件…

动态规划:万变不离其宗,带你吃透股票系列问题

前言&#xff1a; 对于买卖股票问题而言&#xff0c;最关键的是我们对问题的处理方式&#xff08;对于每一天而言&#xff0c;我们应该描述当天买入卖出还是只描述每天股票的只有或者不持有的状态呢&#xff1f;&#xff09;我们应该描述每天股票是否持有的状态&#xff0c;因…

【论文阅读】基于人工智能目标检测与跟踪技术的过冷流沸腾气泡特征提取

Bubble feature extraction in subcooled flow boiling using AI-based object detection and tracking techniques 基于人工智能目标检测与跟踪技术的过冷流沸腾气泡特征提取 期刊信息&#xff1a;International Journal of Heat and Mass Transfer 2024 级别&#xff1a;EI检…

等保测评与商用密码共铸工控安全“双评合规”新篇章

最近听说了一个段子&#xff1a;“网络安全就像美女的内衣&#xff0c;等保和密评就是最贴身的内衣两件套&#xff0c;上下身一件都不能少。否则你的魔鬼身材&#xff08;核心数据&#xff09;就有可能被色狼&#xff08;黑客&#xff09;一览无余&#xff08;数据泄漏&#xf…

将SU模型导入ARCGIS,并获取高度信息,多面体转SHP文件(ARCMAP)

问题:将Sketchup中导出的su模型,导入arcgis并得到面shp文件,进而获取各建筑的高度、面积等信息。 思路: (1)导入arcgis得到多面体 (2)转为面shp文件 (3)计算高度/面积等 1、【3D Analyst工具】【转换】【由文件转出】【导入3D文件】(在此步骤之间,建议先建立一个…

学习磁盘管理

文章目录 一、磁盘接口类型二、磁盘设备的命名三、fdisk分区四、自动挂载五、扩容swap六、GPT分区七、逻辑卷管理八、磁盘配额九、RAID十、软硬链接 一、磁盘接口类型 IDE、SATA、SCSI、SAS、FC&#xff08;光纤通道&#xff09; IDE, 该接口是并口。SATA, 该接口是串口。SCS…

linux系统---nginx(2)rewrite重写功能

目录 一、rewrite概述 1、rewrite功能 2、跳转场景 二、标准配置指令 1、rewrite日志记录指令 2、未初始化变量告警日志记录指令 3、rewrite 指令 3.1 正则表达式 三、rewrite模块使用实例 1.基于域名的跳转 一、rewrite概述 1、rewrite功能 访问重写 rewrite 是 …

分布式事务(7)之Seata简介

一、分布式事务解决方案 2PC即两阶段提交协议&#xff0c;是将整个事务流程分为两个阶段&#xff0c;准备阶段&#xff08;Prepare phase&#xff09;、提交阶段&#xff08;commit phase&#xff09;&#xff0c;2是指两个阶段&#xff0c;P是指准备阶段&#xff0c;C是指提交…

C# 发现同一依赖程序集的不同版本间存在冲突。请将项目文件中的“AutoGenerateBindingRedirects”属性设置为 true

C# 发现同一依赖程序集的不同版本间存在冲突。请将项目文件中的“AutoGenerateBindingRedirects”属性设置为 true Severity Code Description Project File Line Suppression State Warning Found conflicts between different versions of the same dependent assembly. P…