设计模式 |10 模板方法模式

本贴最后更新于 1667 天前,其中的信息可能已经东海扬尘

开头说几句

博主的博客地址: https://www.jeffcc.top/
博主学习设计模式用的书是 Head First 的《设计模式》,强烈推荐配套使用!

什么是模板方法模式

权威定义:
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
博主的理解:
模板方法其实就是封装了不变的部分,对外开放了对变化的部分的拓展,通过使用 final 修饰的方法达到实现的一个算法的骨架,并且将算法的需要被延迟加载的方法修饰为 abstract 方法。同时也可以使用钩子的思路来进一步拓展。

模板方法模式与工厂模式

回顾工厂方法模式定义:工厂方法模式定义了一个创建对象的接口,但是由子类决定实例化的类是哪一个。工厂方法让实例化延迟到了子类。

相似之处:大家都是将实现延迟到了子类,并且将不变的部分封装在父类,并且都是通过抽象类的继承方式实现的。
不同之处:模板方法是通过 final 修饰的算法骨架实现的,而我们的工厂方法模式是通过定义的不变部分到顶层工厂中,并没有限制子类的对于父类的工厂的修改。

设计原则

  1. 依赖倒置原则,要依赖抽象,不要依赖具体类,具体做法是需要高层组件(工厂)和底层组件(实现类)之间不要有太多的依赖关系,而是可以通过一个共同的抽象类(工厂产生的对象)来实现依赖倒置。
  2. 多用组合,少用继承。
  3. 针对接口编程,而不是针对实现编程。
  4. 为交互对象之间的松耦合设计而努力。
  5. 类应该对外开放拓展,对修改关闭。
  6. 依赖抽象,不依赖具体类。
  7. 最少知识原则。
  8. 好莱坞原则:别来找我,我会找你的;意思是将决策权交给高层模块中,以决定什么时候调用底层模块,在模板方法中体现就是使用倒钩的方法,让底层的组件将自己倒挂在高层超类中,而是否调用是通过顶层决定的。

设计要点

  1. 巧用钩子,好莱坞原则;
  2. 算法核心要通过 final 修饰以防被修改;
  3. 必须子类实例化的方法需要通过 abstract 修饰;

设计实例

设计背景

设计一个制作奶茶的模板方法,并且实例化几款具体的奶茶制作;

设计思路

首先我们需要将制作奶茶的大体步骤抽取出来,作为核心算法:
1、增加配料(延迟到子类)
2、加水(公共部分)
3、搅拌机搅拌(公共部分)
4、加冰(使用钩子,不是每个都要加 但是默认加冰)
5、包装机包装(公共部分)
6、摇匀(公共部分)
7、提醒奶茶制作成功(延迟到子类)

项目类图

image.png

项目结构

image.png

奶茶模板方法

package milkytea;

/**
 * 奶茶的制作超类 模板方法
 */
public abstract class MilkyTea {

    /**
     * 抽象出一层final的制作奶茶的算法
     */
    public final void createMilkyTea() {
        addIngredients();
        addWater();
        stir();
        //钩子函数的体现 好莱坞原则的体现
        if(addIceOrNot()){
            addIce();
        }
        packaging();
        shakeUp();
        makeSuccess();
    }

    /**
     * 添加配料的方法
     * 延迟到子类实现
     */
    public abstract void addIngredients();

    /**
     * 添加水
     * 公共部分
     * 父类提供实现
     */
    public void addWater() {
        System.out.println("奶茶加水成功!");
    }

    /**
     * 搅拌
     * 公共部分
     * 父类提供实现
     */
    public void stir() {
        System.out.println("奶茶搅拌成功!");
    }

    /**
     * 加冰
     * 使用钩子(在算法中体现)
     */
    public void addIce() {
        System.out.println("奶茶加冰成功!");
    }

    /**
     * 使用一个方法来让子类实现,是否加冰
     *
     * @return true 加冰 false 不加
     */
    public boolean addIceOrNot() {
        return true;
    }

    /**
     * 包装
     * 公共部分
     * 父类提供实现
     */
    public void packaging() {
        System.out.println("奶茶包装成功!");
    }

    /**
     * 摇匀
     * 公共部分
     * 父类提供实现
     */
    public void shakeUp() {
        System.out.println("奶茶摇匀成功!");
    }

    /**
     * 制作成功的提醒
     * 延迟到子类
     */
    public abstract void makeSuccess();

}

珍珠奶茶实现

package milkytea.impl;

import milkytea.MilkyTea;

/**
 * 制作珍珠奶茶
 */
public class PearlMilkyTea extends MilkyTea {
    /**
     * 添加原料
     */
    @Override
    public void addIngredients() {
        System.out.println("珍珠奶茶添加:珍珠+椰果");
    }

    @Override
    public void makeSuccess() {
        System.out.println("珍珠奶茶制作成功!");
    }
}

草莓奶茶实现

package milkytea.impl;

import milkytea.MilkyTea;

/**
 * 制作草莓奶茶
 */
public class StrawberryMilkyTea extends MilkyTea {
    /**
     * 添加原料
     */
    @Override
    public void addIngredients() {
        System.out.println("草莓奶茶添加:草莓+果冻");
    }

    //体现钩子 重写不加冰
    @Override
    public boolean addIceOrNot() {
        return false;
    }

    @Override
    public void makeSuccess() {
        System.out.println("草莓奶茶制作成功!");
    }
}

测试

package test;

import milkytea.MilkyTea;
import milkytea.impl.PearlMilkyTea;
import milkytea.impl.StrawberryMilkyTea;

/**
 * 测试做奶茶
 */
public class MakeMikyTea {
    public static void main(String[] args) {
        //创建珍珠奶茶实例
        MilkyTea peralMilkyTea = new PearlMilkyTea();
        //创建草莓奶茶实例
        MilkyTea strawberryMilkyTea = new StrawberryMilkyTea();

        //做奶茶
        peralMilkyTea.createMilkyTea();
        System.out.println("-----------------------------");
        strawberryMilkyTea.createMilkyTea();

    }
}

输出

珍珠奶茶添加:珍珠+椰果
奶茶加水成功!
奶茶搅拌成功!
奶茶加冰成功!
奶茶包装成功!
奶茶摇匀成功!
珍珠奶茶制作成功!
-----------------------------
草莓奶茶添加:草莓+果冻
奶茶加水成功!
奶茶搅拌成功!
奶茶包装成功!
奶茶摇匀成功!
草莓奶茶制作成功!

Process finished with exit code 0

回到定义

相信大家看完上面写的小 demo 可以很明显的看出模板方法的特点了:核心算法通过 final 修饰防止被篡改,算法中不变的部分由父类实现,变化的部分封装成 abstract 方法延迟到子类实现,钩子函数的巧妙利用实现了子类管理是否使用父类的组件,在这里我们的父类是一个底层组件,子类是属于高层组件,所以我们实现了好莱坞原则。


END
2019 年 9 月 26 日 10:50:01

  • 设计模式

    设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

    198 引用 • 120 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...