当前位置: 首页 > news >正文

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

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

日常叨逼叨

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


相关文章:

  • Python中的异常处理详解及try-except语句的工作原理
  • C++ 网络编程学习二
  • 2/22作业
  • 【leetcode每日一题】【滑动窗口长度固定】案例
  • 【ArcGIS】基于DEM/LUCC等数据统计得到各集水区流域特征
  • MapInfo格式到ArcGIS格式的转换方法
  • js之数组遍历
  • 性能优化——canvas 加载海量图
  • Open CASCADE学习|视图
  • Python零基础安装教程(包含各种安装方案)以及PyCharm安装步骤
  • 第七章 正则表达式
  • 机器人内部传感器阅读笔记及心得-位置传感器-旋转变压器、激光干涉式编码器
  • C++多态
  • 理想滤波器、巴特沃斯滤波器、高斯滤波器实现(包含低通与高通,代码实现与分析)
  • opengl播放3d pose 原地舞蹈脚来回飘动
  • C++拿几道题练练手吧
  • java面试题之mybatis篇
  • 浅谈 TCP 三次握手
  • 【牛客】SQL123 SQL类别高难度试卷得分的截断平均值
  • 支持国密ssl的curl编译和测试验证(下)
  • 日韩 “打头阵”与美国贸易谈判,汽车、半导体产业忧虑重重
  • 外交部:印度香客赴中国西藏神山圣湖朝圣将于今年夏季恢复
  • 幸福航空取消“五一”前航班,财务人员透露“没钱飞了”
  • 张译、惠英红分获第二十届中国电影华表奖优秀男、女演员奖
  • 我国首个大型通用光谱望远镜JUST在青海启动建设
  • 俄联邦安全局:俄军高级官员汽车爆炸案嫌疑人已被捕