15.模版模式设计思想

目录介绍
  • 01.模版模式基础

    • 1.1 模版模式由来
    • 1.2 模版模式定义
    • 1.3 模版模式场景
    • 1.4 模版模式思考
    • 1.5 模版模式特点
    • 1.6 理解模版唯一性
    • 1.7 主要解决问题
  • 02.模版模式原理

    • 2.1 罗列一个场景
    • 2.2 用例子理解模版
    • 2.3 需求普通实现
    • 2.4 案例演变实现
    • 2.5 模版模式实现步骤
  • 03.模版模式结构

    • 3.1 模版标准案例
    • 3.2 模版模式结构
    • 3.3 模版模式时序图
  • 04.模版模式案例分析

    • 4.1 角色分析
    • 4.2 拓展一个场景
    • 4.3 模版模式实践
  • 05.模版者模式分析

    • 5.1 模版模式优点
    • 5.2 模版模式缺点
    • 5.3 使用建议说明
    • 5.4 思考题考察
  • 06.观察者模式总结

    • 6.1 总结一下学习
    • 6.2 更多内容推荐

推荐一个好玩网站

一个最纯粹的技术分享网站,打造精品技术编程专栏!编程进阶网

https://yccoding.com/

关于设计模式,所有的代码都放到了该项目。设计模式大全

01.模版模式基础介绍

1.0 本博客AI摘要

模版模式是一种行为设计模式,它定义了一个操作中的算法骨架,而将一些步骤延迟到子类中实现。这种方式让子类可以在不改变算法结构的情况下重新定义算法的某些特定步骤。文章详细介绍了模版模式的基础概念、应用场景、实现原理及优缺点,并通过具体案例深入解析了模版模式的使用方法。适合初学者和有一定经验的开发者深入学习。

1.1 模版模式由来

在软件构建过程中,对于某一项任务,它常常有稳定的整体操作结构,但各个子步骤却有很多改变的需求,或者由于固有的原因(比如框架与应用之间的关系)而无法和任务的整体结构同时实现。

如何在确定稳定操作结构的前提下,来灵活应对各个子步骤的变化或者晚期实现需求?如何保证架构逻辑的正常执行,而不被子类破坏 ?这个时候可以用模版模式!

1.2 模版模式定义

模板方法模式是类的行为模式。

模板模式(Template)将定义的算法抽象成一组步骤,在抽象类种定义算法的骨架,把具体的操作留给子类来实现。

模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

1.3 模版模式场景

常见模板模式运用场景如下:

  1. Java Servlet:HttpServlet 就是使用了模板模式。HttpServlet 类定义了 service() 方法作为模板方法,子类需要实现 doGet()、doPost() 等方法来处理具体的 HTTP 请求。
  2. Spring Framework:例如,JdbcTemplate 使用了模板模式来简化 JDBC 数据访问代码,定义了模板方法来执行数据库操作,具体的 SQL 语句和参数由子类提供。

总结:当存在一些通用的方法,可以在多个子类中共用时。

1.4 模版模式思考

实际开发中常常会遇到,代码骨架类似甚至相同,只是具体的实现不一样的场景。

例如:休假流程都有开启、编辑、驳回、结束。共享单车都是先开锁、骑行、上锁、付款。每个流程都包含这几个步骤,不同的是不同的流程实例它们的内容不一样。你该如何设计?

1.5 模版模式特点

模板模式的主要特点包括:

  1. 定义一个算法的骨架,将一些步骤留给子类实现。
  2. 允许子类在不改变算法结构的基础上,重新定义某些步骤。
  3. 适用于处理包含一系列基本相同步骤的过程,但某些步骤可能有不同的实现。

1.6 主要解决问题

解决在多个子类中重复实现相同的方法的问题,通过将通用方法抽象到父类中来避免代码重复。

02.模版模式原理

2.1 罗列一个场景

以生活中买菜做饭的例子来写个Demo,烧饭一般都是买菜、洗菜、烹饪、装盘四大过程。中国自古有八大菜系,制作方式肯定都避不开这四个过程。那在模板模式中如何实现呢?

2.2 用例子理解模版

这些大的步骤固定,不同的是每个实例的具体实现细节不一样。这些类似的业务我们都可以使用模板模式实现。

2.3 需求普通实现

关于实现买菜做饭,做川菜和徽菜。最原始的实现如下所示:

private void cook() {
    System.out.println("----------川菜制作------------");
    Cooking chuanCaiService = new Cooking(0);
    chuanCaiService.process();
    System.out.println("-----------徽菜制作-----------");
    Cooking huiCaiService = new Cooking(1);
    huiCaiService.process();
}

public class Cooking {

    //type是0表示川菜
    //type是1表示徽菜
    private int type;
    public Cooking(int type) {
        this.type = type;
    }

    public void process() {
        shopping();
        wash();
        cooking();
        dishedUp();
    }

    protected void shopping() {
        if (type == 0) {
            System.out.println("买菜:黑猪肉一斤,蒜头5个");
        } else if (type == 1){
            System.out.println("买菜:新鲜鱼一条,红辣椒五两");
        }

    }

    protected void wash() {
        if (type == 0) {
            System.out.println("清洗:猪肉洗净,蒜头去皮");
        } else if (type == 1){
            System.out.println("清洗:红椒洗净切片,鱼头半分");
        }

    }

    protected void cooking() {
        if (type == 0) {
            System.out.println("烹饪:大火翻炒,慢火闷油");
        } else if (type == 1){
            System.out.println("装盘:用长形盘子装盛");
        }
    }

    protected void dishedUp() {
        if (type == 0) {
            System.out.println("装盘:深碗盛起,热油浇拌");
        } else if (type == 1){
            System.out.println("烹饪:鱼头水蒸,辣椒过油");
        }
    }
}

你有没有发现这样写,要是后期在拓展一个鲁菜,粤菜。这样就会修改原代码,会破坏代码的开闭原则。或者我想修改一下不同菜系的做菜步骤,那就导致代码非常难以修改。

2.4 案例演变实现

创建一个抽象类,它的模板方法被设置为 final。为防止恶意操作,一般模板方法都加上 final 关键词。

public abstract class AbstractCookingService {
    //买菜
    protected abstract void shopping();
    //清洗
    protected abstract void wash();
    //烹饪
    protected abstract void cooking();
    //装盘
    protected abstract void dishedUp();

    public final void process() {
        shopping();
        wash();
        cooking();
        dishedUp();
    }
}

创建实现了上述抽象类的子类。

/**
 * 徽菜制作大厨
 */
public class HuiCaiChef extends AbstractCookingService {

    @Override
    protected void shopping() {
        System.out.println("买菜:新鲜鱼一条,红辣椒五两");
    }

    @Override
    protected void wash() {
        System.out.println("清洗:红椒洗净切片,鱼头半分");
    }

    @Override
    protected void cooking() {
        System.out.println("烹饪:鱼头水蒸,辣椒过油");
    }

    @Override
    protected void dishedUp() {
        System.out.println("装盘:用长形盘子装盛");
    }
}

/**
 * 川菜制作大厨
 */
public class ChuanCaiChef extends AbstractCookingService {

    @Override
    protected void shopping() {
        System.out.println("买菜:黑猪肉一斤,蒜头5个");
    }

    @Override
    protected void wash() {
        System.out.println("清洗:猪肉洗净,蒜头去皮");
    }

    @Override
    protected void cooking() {
        System.out.println("烹饪:大火翻炒,慢火闷油");
    }

    @Override
    protected void dishedUp() {
        System.out.println("装盘:深碗盛起,热油浇拌");
    }
}

然后测试演示一下

private void test() {
    System.out.println("----------川菜制作------------");
    AbstractCookingService chuanCaiService = new ChuanCaiChef();
    chuanCaiService.process();
    System.out.println("-----------徽菜制作-----------");
    AbstractCookingService huiCaiService = new HuiCaiChef();
    huiCaiService.process();
}

在模板模式中,定义了一个算法的框架,将算法的具体步骤延迟到子类中实现。这种模式允许子类重写算法的特定步骤而不改变算法的结构。

模板模式中通常包含两个角类,一个模板类和一个具体类,模板类就是一个算法框架。

2.5 模版模式实现步骤

模板模式也没什么高深莫测的,简单来说就是三大步骤:

  1. 创建一个抽象类,定义几个抽象方法和一个final修饰的模板方法,而模板方法中设定了抽象方法的执行顺序或逻辑。
  2. 无论子类有多少个,只需要继承该抽象类,实现父类的抽象方法重写自己的业务。
  3. 根据不同的需求创建不同的子类实现,每次调用的地方只需调用模板方法,即可完成特定的模板流程。

03.模版模式结构

3.1 模版标准案例

3.2 模版模式结构

模板模式的主要角色包括:

  1. 抽象类(AbstractClass):负责实现模板方法,并声明在模板方法中所使用的抽象方法。
  2. 具体类(ConcreteClass):实现抽象类中的抽象方法。
  3. 客户端(Client):使用具体类继承的模板方法。

3.3 模版模式时序图

04.模版模式案例分析

4.1 角色分析

抽象类(Abstract):定义了算法骨架,包含一个或多个抽象方法,这些方法由子类来具体实现。抽象类中通常还包含一个模板方法,用来调用抽象方法和具体方法,控制算法的执行顺序;还可以定义钩子方法,用于在算法中进行条件控制。

具体类(Concrete Class):继承抽象类,实现抽象方法。

4.2 拓展一个场景

以订外卖为例,解释一下模板模式。假设订外卖的过程包含三个步骤:选择外卖、支付、取外卖、是否打赏。

我们可以定义一个OderFood的抽象类,那么选择外卖就可以是抽象方法,需要子类取实现它,支付和取外卖可以定义为具体方法,另外是否打赏为钩子方法,子类可以决定是否对算法的不同进行挂钩。

还需要定义一个模板方法,用以控制流程;不同的商家,如KFC、星巴克就是具体类。

4.3 模版模式实践

OrderFood——抽象类(Abstract)

/**
 * @author Created by njy on 2023/6/24
 * 1.抽象类(Abstract Class):点外卖
 * 包含选择外卖、支付、取外卖三个方法,
 * 其中选择外卖为抽象方法,需要子类实现
 * 支付、取外卖为具体方法
 * 另外是否打赏为钩子方法,子类可以决定是否对算法的不同进行挂钩
 */
public abstract class OrderFood {

    //模板方法
    public void order(){
        selectFood();
        pay();
        getFood();
    }
    //选择外卖   抽象方法 由子类实现具体细节
    public abstract void selectFood();
    //是否打赏   钩子方法 可以重写来做条件控制
    public boolean isGiveAward(){
        return false;
    }
    //-------具体方法----------
    public void pay(){
        System.out.println("支付成功,外卖小哥正在快马加鞭~~");
    }

    //取外卖
    public void getFood(){
        System.out.println("取到外卖");
        if (isGiveAward()){
            System.out.println("打赏外卖小哥");
        }
    }
}

具体类(Concrete Class)

/**
 * 具体类(Concrete Class):星巴克
 */
public class Starbucks extends OrderFood{

    //实现父类方法
    @Override
    public void selectFood() {
        System.out.println("一杯抹茶拿铁");
    }

    //重写钩子方法,打赏外卖小哥
    @Override
    public boolean isGiveAward(){
        return true;
    }
}

/**
 * 具体类(Concrete Class):KFC
 */
public class KFC extends OrderFood{
    @Override
    public void selectFood() {
        System.out.println("一份汉堡炸鸡四件套");
    }
}

测试一下,代码如下:

//星巴克(重写了钩子方法,打赏外卖小哥)
OrderFood orderFood=new Starbucks();
orderFood.order();
System.out.println("--------KFC------------");
//KFC
OrderFood orderFood1=new KFC();
orderFood1.order();

05.模版者模式分析

5.1 模版模式优点

  1. 封装不变部分:算法的不变部分被封装在父类中。
  2. 扩展可变部分:子类可以扩展或修改算法的可变部分。
  3. 提取公共代码:减少代码重复,便于维护。

5.2 模版模式缺点

  1. 类数目增加:每个不同的实现都需要一个子类,可能导致系统庞大。

5.3 使用建议说明

  1. 当有多个子类共有的方法且逻辑相同时,考虑使用模板方法模式。
  2. 对于重要或复杂的方法,可以考虑作为模板方法定义在父类中。

注意事项:为了防止恶意修改,模板方法通常使用final关键字修饰,避免被子类重写。

5.4 思考题考察

需求1: 实现字符打印和字符串打印两种不同的打印样式。

分析和实现:定义一个抽象类AbstractDisplay其中包含模板方法display,两个实现模板的具体类,CharDisplay和StringDisplay。具体可以看:TemplateDesign

需求2: 做菜的步骤一般是:洗锅 --> 炒菜 --> 洗碗 ,不同的菜,只是炒菜这一个步骤具体细节是不同的。然后做一个煮糖醋鲤鱼,小炒肉,你该如何实现?

分析和实现:整体流程是一样的,有些步骤一样,有些步骤不一样,但是不使用模板模式,需要每个类都重写一遍方法,这个时候可以用模版模式实现。具体可以看:TemplateDesign

06.观察者模式总结

6.1 总结一下学习

  1. 定义:在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。
  2. 意图:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤
  3. 主要解决:一些方法通用,却在每一个子类都重新写了这一方法。
  4. 何时使用:有一些通用的方法。
  5. 如何解决:将这些通用算法抽象出来。
  6. 关键代码:在抽象类实现,其他步骤在子类实现。
  7. 优点: 1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。
  8. 缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
  9. 使用场景: 1、有多个子类共有的方法,且逻辑相同。 2、重要的、复杂的方法,可以考虑作为模板方法。

6.2 更多内容推荐

模块描述备注
GitHub多个YC系列开源项目,包含Android组件库,以及多个案例GitHub
博客汇总汇聚Java,Android,C/C++,网络协议,算法,编程总结等YCBlogs
设计模式六大设计原则,23种设计模式,设计模式案例,面向对象思想设计模式
Java进阶数据设计和原理,面向对象核心思想,IO,异常,线程和并发,JVMJava高级
网络协议网络实际案例,网络原理和分层,Https,网络请求,故障排查网络协议
计算机原理计算机组成结构,框架,存储器,CPU设计,内存设计,指令编程原理,异常处理机制,IO操作和原理计算机基础
学习C编程C语言入门级别系统全面的学习教程,学习三到四个综合案例C编程
C++编程C++语言入门级别系统全面的教学教程,并发编程,核心原理C++编程
算法实践专栏,数组,链表,栈,队列,树,哈希,递归,查找,排序等Leetcode
Android基础入门,开源库解读,性能优化,Framework,方案设计Android

23种设计模式

23种设计模式 & 描述 & 核心作用包括
创建型模式
提供创建对象用例。能够将软件模块中对象的创建和对象的使用分离
工厂模式(Factory Pattern)
抽象工厂模式(Abstract Factory Pattern)
单例模式(Singleton Pattern)
建造者模式(Builder Pattern)
原型模式(Prototype Pattern)
结构型模式
关注类和对象的组合。描述如何将类或者对象结合在一起形成更大的结构
适配器模式(Adapter Pattern)
桥接模式(Bridge Pattern)
过滤器模式(Filter、Criteria Pattern)
组合模式(Composite Pattern)
装饰器模式(Decorator Pattern)
外观模式(Facade Pattern)
享元模式(Flyweight Pattern)
代理模式(Proxy Pattern)
行为型模式
特别关注对象之间的通信。主要解决的就是“类或对象之间的交互”问题
责任链模式(Chain of Responsibility Pattern)
命令模式(Command Pattern)
解释器模式(Interpreter Pattern)
迭代器模式(Iterator Pattern)
中介者模式(Mediator Pattern)
备忘录模式(Memento Pattern)
观察者模式(Observer Pattern)
状态模式(State Pattern)
空对象模式(Null Object Pattern)
策略模式(Strategy Pattern)
模板模式(Template Pattern)
访问者模式(Visitor Pattern)

6.3 更多内容


杨充
221 声望42 粉丝