浅析设计模式4——模板方法模式

news2024/11/17 11:34:59

4f84513d6019757a15a6cf8d27e50cb5.gif

我们在进行软件开发时要想实现可维护、可扩展,就需要尽量复用代码,并且降低代码的耦合度。设计模式就是一种可以提高代码可复用性、可维护性、可扩展性以及可读性的解决方案。大家熟知的23种设计模式,可以分为创建型模式、结构型模式和行为型模式三大类。本专题着眼于实际开发过程中常用的几种设计模式,从理论和实战两个角度进行讨论和分享,力求逻辑清晰、表述简洁,帮助大家在项目中合理运用设计模式,保障代码的可靠性。

本文为此系列第四篇文章,前三篇见——

第一篇:浅析设计模式1 —— 工厂模式

第二篇:浅析设计模式2 —— 策略模式

第三篇:浅析设计模式3 —— 装饰者模式

127f80fa5089dd4d98713ef588df9e1d.png

概述

我们在进行软件开发时要想实现可维护、可扩展,就需要尽量复用代码,并且降低代码的耦合度,而设计模式就是一种可以提高代码可复用性、可维护性、可扩展性以及可读性的解决方案。

7338390c26247f4ddfeec19a7bde5738.png

大家熟知的23种设计模式,可以分为创建型模式、结构型模式和行为型模式三大类。其中, 行为型模式 可用于描述程序中多个类和多个对象如何协作完成复杂的任务,涉及不同对象间的职责分配、算法的抽象化。模版方法模式是一种应用广泛的行为型模式,本文将着眼于模版方法模式进行学习分享,阐述模板方法模式的核心概念和应用实践。认识和使用模板方法模式,可以帮助我们在设计代码时更好的控制算法流程,避免大量重复代码,从而有效提升编程效率。

36a2932af8234b8696e56e27bd2765cd.png

4c576168c8af0ed24170d08ce754ea8c.png

基本概念

模版方法模式的核心思想是:首先在抽象类中定义一个任务的算法骨架,将算法的执行细节延迟到子类中个性化实现。注意,子类可以在不改变算法架构的情况下,重新定义特定步骤,甚至干预算法的执行流程,从而起到控制抽象父类行为的作用。

下面从模式结构和使用步骤两个层面,简单阐述模版方法模式的基本概念。

  结构

模版方法模式的结构相对简单,主要包含两大类:抽象类、具体类,抽象父类中首先定义好算法流程,具体的步骤细节延迟到具体子类中执行。

角色
关系
作用

抽象类    Abstract Class

具体类的父类

定义一个抽象的模版类,给出算法的骨架,包含一个模版方法和若干个基本方法(抽象方法、具体方法、钩子方法)

具体类    Concrete Class

抽象构件的接口实现类

定义一个具体的实现类,实现抽象类中定义的抽象方法和钩子方法。

90d793527c29652670f464206f4ccfb3.png

  使用

有了上述的基本概念,我们将装饰者模式的使用步骤概括为:

step1:创建抽象类,定义一个算法骨架,包含一个模版方法和若干个基本方法:

模版方法:算法流程,定义基本方法的执行顺序;

基本方法 - 具体方法:在抽象类中实现,可被具体类继承或重写;

基本方法 - 抽象方法:在抽象类中声明,由具体类实现;

基本方法 - 钩子方法:在抽象类中实现,包含一种用于判断的方法、一种由具体类重写的空方法。

step2:创建具体类,实现抽象类中定义的抽象方法和钩子方法;

3f749df228b349e15935163af2533135.png

使用示例

这里还是为网购为例,简单阐述如何使用模版方法模式。我们在网购时,基本流程都是先选择一个商品、确定样式或型号、下单支付(也可能会和购物车其它商品一起支付)、收货(如果不满意还可退换货)、终止一单网购交易。当然这个过程可能还会掺杂多种其它情况,我们将购物流程简化为:选择商品、下单支付、收货(退换货)、终止交易,实现如下。

  代码实现

// 创建抽象父类,定义算法骨架
public abstract class TMAbstractClass {


    public final void shopOnline() {
        selectItems();
        checkoutItems();
        if(isReturnItemsHook1()) {
            returnItems();
        } else if(isExchangeItemsHook2()) {
            exchangeItems();
            //假设一次性换到满意的商品
            confirmTheReceipt();
        } else {
            confirmTheReceipt();
        }
        terminateTransaction();
    }


    protected abstract void selectItems();


    protected void checkoutItems() {
        System.out.println("下单支付");
    }


    public boolean isReturnItemsHook1() {
        return false;
    }
    protected void returnItems() {
    }


    public boolean isExchangeItemsHook2() {
        return false;
    }
    protected void exchangeItems() {
    }


    protected void confirmTheReceipt() {
        System.out.println("确认收货");
    }


    protected void terminateTransaction() {
        System.out.println("终止交易");
    }


}


// 定义具体子类1:购买商品1
public class TMConcreteClass1 extends TMAbstractClass{


    @Override
    protected void selectItems() {
        System.out.println("选择商品1");
    }


    @Override
    public boolean isReturnItemsHook1() {
        return true;
    }


    @Override
    protected void returnItems() {
        System.out.println("退货退款");
    }


}


// 定义具体子类2:购买商品2
public class TMConcreteClass2 extends TMAbstractClass{


    @Override
    protected void selectItems() {
        System.out.println("选择商品2");
    }


    @Override
    public boolean isExchangeItemsHook2() {
        return true;
    }


    @Override
    protected void exchangeItems() {
        System.out.println("换货");
    }
}


//客户端调用
public class userPayForItem() {
    public static void main(String[] args) {
        System.out.println("购物记录1:");
        TMAbstractClass shopRecord1 = new TMConcreteClass1();
        shopRecord1.shopOnline();
        System.out.println();
        System.out.println("购物记录2:");
        TMConcreteClass2 shopRecord2 = new TMConcreteClass2();
        shopRecord2.shopOnline();      
    }
}

  结果输出

购物记录1:
选择商品1
下单支付
退货退款
终止交易


购物记录2:
选择商品2
下单支付
换货
确认收货
终止交易

  UML图

16a5f8aab35701b88f6aa12c28bd7094.png

31cca11e8f741ed5bdce4f8461c8483e.png

JDK源码赏析

模板方法模式在框架源码中使用也很广泛,比如:JDK 源码中 AbstractList 抽象类、Mybatis 源码中 BaseExecutor 抽象类。本文以 AbstractList 为例,分析模版方法模式在源码中如何应用。AbstractList 是 ArrayList 的父类,也就是模版类,它包含的方法有很多,这里主要介绍一下 addAll() 方法。

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
    public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);
        boolean modified = false;
        for (E e : c) {
            add(index++, e);
            modified = true;
        }
        return modified;
    }
}

事实上,AbstractList 中的方法除了一些私有方法不能被子类访问,大多数方法都和 addAll() 一样,可由子类选择是否修改:如果子类需要做个性化的实现就要修改,如果不需要则直接按照父类方法的逻辑执行。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);


        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount


        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);


        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }
}

另外,AbstractList 中有一个 get() 方法,明确要求子类修改实现,如下所示:

private final AbstractList<E> l;
private final int offset;


abstract public E get(int index);


public E get(int index) {
        rangeCheck(index);
        checkForComodification();
        return l.get(index+offset);
}

左边是父类AbstractList中的,右边是ArrayList中的方法。在父类中没有直接写出实现代码,而是让子类自己手动去实现。除此之外其实还有一个方法就是AbstractList父类AbstractCollection中的toString方法。在ArrayList中是没有的,但是平常在写代码时候,是可以直接调用的,这就是一个公共的方法。

0f0e20007009b5cc8ba4a22b529bc47b.png

优缺点及适用场景

▐  优点

  1. 封装不变部分, 扩展可变部分。把认为是不变部分的算法封装到父类实现, 而可变部分的则可以通过继承来继续扩展。

  2. 提取公共部分代码, 便于维护。

  3. 行为由父类控制, 子类实现。基本方法是由子类实现的, 因此子类可以通过扩展的方式增加相应的功能, 符合开闭原则。

  缺点

  1. 子类执行结果影响父类结果,这违背了我们平时设计代码的习惯,在复杂项目中,很可能会带来阅读上的难度。

  2. 可能引起子类泛滥、为了继承而继承的问题。

  适用场景

  1. 算法的整体步骤相对固定、其中个别方法容易变化时,这时候可以使用模板方法模式,将易变部分抽象出来,供子类实现。

  2. 当多个子类存在公共行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。

  3. 当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,即可进行个性化的功能扩展。

e92901462df2a6d4592dd34a357ef2ea.png

总结

模板方法模式只需要简单的继承关系就可以完成,是一种比较简单易用的设计模式。我们在平常写代码时,很可能也会经常使用模板方法模式。根据上文所述,如果我们希望子类不要修改父类的方法,只需要加上 final 修饰即可;如果希望子类一定重写父类的方法,就将父类的方法用 abstract 修饰;如果子类可以修改也可以不修改,就可以参照 addAll() 方法那样设计。模板方法的重点是要理解模板,这个模板需要尽量使用抽象类。因为抽象类比接口更加灵活,能更好得定义模板。

b877777b88705576c1125422bc4a95a3.png

团队介绍

我们是大聚划算技术团队。负责支持聚划算、百亿补贴、天天特卖等业务。我们聚焦优惠和选购体验,通过数智化驱动形成更有效率和确定性的货品运营方法论,为消费者提供精选和极致性价比的商品,为商家提供更具爆发确定性的营销方案。

¤ 拓展阅读 ¤

3DXR技术 | 终端技术 | 音视频技术

服务端技术 | 技术质量 | 数据算法

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

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

相关文章

OAuth2简介

目录一、 什么是OAuth2二、OAuth2中的角色三、认证流程四、生活中的Oauth2思维五、令牌的特点六、OAuth2授权方式1、授权码2、隐藏方式3、密码方式4、凭证方式一、 什么是OAuth2 OAuth2.0是目前使用非常广泛的授权机制&#xff0c;用于授权第三方应用获取用户的数据。 举例说明…

使用Vuex的个人理解

一、逻辑原理 要使用 Vuex 进行集中管理数据&#xff08;状态&#xff09;&#xff0c;按照 Vuex 分模块的设 计思想&#xff0c;先在 src 下创建 store 文件夹&#xff0c;然后创建一个根级别的 index.js&#xff0c;作为组装模块并导出 store 地方&#xff08;store 对象是 …

xss.haozi.me通关教程

10.xss.haozi.me通关教程 0x00 首先整体浏览网站 分别是xss注入点&#xff0c;注入后的HTML代码以及网页源码 构造常规payload&#xff1a; <script>alert(1)</script> 成功通关 0x01 看到注入点是在标签中, 所以用上一题的方法是不会被解析的, 故需要去构造…

MySQL_索引

索引概述 简介 索引&#xff08;index&#xff09;是帮助MySQL高效获取数据的数据结构&#xff08;有序&#xff09;。在数据之外&#xff0c;数据库系统还维护着满足特定查找算法的数据结构&#xff0c;这些数据结构以某种方式引用&#xff08;指向&#xff09;数据&#xff…

OKR之剑·实战篇04:OKR执行过程优化的那些关键事

作者&#xff1a;vivo 互联网平台产品研发团队 本文是《OKR 之剑》系列之实战第 4 篇——OKR执行过程不是一成不变的&#xff0c;团队和个人在执行中不断优化执行的具体行动&#xff0c;保障OKR的高效执行。 前言 “言治骨角者&#xff0c;既切之而复磋之&#xff1b;治玉石者…

vue axios post 请求415、400、500、404报错时

vue axios post 请求415 415错误的解释&#xff0c;服务器无法处理请求附带的媒体格式。 解决方式&#xff1a; 后端参数使用了RequestBody注解进行绑定&#xff0c;用了这个注解进行数据绑定&#xff0c;只能接受数据类型为 Content-Type类型为application/json 1.后台修改&am…

notepad++ 中安装NppExec插件

一、何为NppExec简单的说&#xff0c;这个插件可以让用户在NPP中直接运行一些命令和程序&#xff0c;而不用启动这些命令和程序对应的实际工具或编译器。1. NppExec是...NppExec是介于Notepad和外部工具/编译器之间的一个中间件。它允许用户在NPP中直接运行这些工具/编译器。Np…

智能管理PoE交换机

随着网络化信息化的快速发展&#xff0c;以太网供电&#xff08;PoE&#xff09;的优势逐渐被大家所熟知。只需要一根网线&#xff0c;PoE在给网络设备供电的同时还能传输数据&#xff0c;免布线&#xff0c;节省成本和空间&#xff0c;设备可随意移动&#xff0c;系统部署灵活…

TCP/IP网络编程——域名及网络地址

完整版文章请参考&#xff1a; TCP/IP网络编程完整版文章 文章目录第 8 章 域名及网络地址8.1 域名系统8.1.1 什么是域名8.1.2 DNS 服务器8.2 IP地址和域名之间的转换8.2.1 程序中有必要使用域名吗&#xff1f;8.2.2 利用域名获取IP地址第 8 章 域名及网络地址 8.1 域名系统 …

重建大师可以实现识别地表树木吗?对数据格式有什么要求?

目前重建大师5.0具备自动单体化与切割单体化功能&#xff0c;在三维重建的过程中就可自动识别地表树木、建筑。 数据格式的话&#xff0c;目前是需要重建大师生产的倾斜模型数据&#xff0c;其他软件生产的模型暂不支持。重建大师目前并不是直接对模型数据进行深度学习识别&am…

Node.js包和模块的区别在哪儿

在Node.js 中&#xff0c;会将某个独立的功能封装起来&#xff0c;用于发布、更新、依赖管理和进行版本控制。Nodejs 根据CommonJS规范实现了包机制&#xff0c;开发了NPM包管理工具&#xff0c;用来解决包的发布和获取需求。 Node.js的包和模块并没有本质的不同&#xff0c;包…

详解linux中网络的几种模式:NAT,网桥,以及静态IP的配置和主机名

NAT模式 NAT就是网络地址转换&#xff0c;虚拟机和主机构建一个专有网络&#xff0c;通过NAT进行设备IP的转换&#xff0c;虚拟机通过共享主机的IP访问外界网络&#xff0c;但外部网络无法访问虚拟机。构建出的子网一般是WNET8. 网桥模式 也叫桥接模式&#xff0c;虚拟机直接…

Android发送广播时报错:Sending non-protected broadcast xxxxxxx from system xxxxxxxxxx

带android:sharedUserId“android.uid.system” 发送广播时&#xff0c;会出现 Sending non-protected broadcast 异常提醒&#xff1b; 原因&#xff1a; Ams在发送广播时&#xff0c;对于systemApp(系统应用)&#xff0c;会要求发送广播必须是声明在frameworks\base\core\re…

【实战项目】Django-Vue007---Redis、Python操作redis之普通连接和连接池、redis操作各种数据、django中使用redis

回顾 1、用户登录注册相关5个接口 多方式登录接口 手机号是否存在接口 发送验证码接口 验证码登录接口 验证码注册接口2、可以写验证码注册登录接口 如果手机号存在&#xff0c;直接登录成功 如果手机号不存在&#xff0c;直接创建用户&#xff0c;并且成功登录&#xff08;…

【计算机基础学科】操作系统学习笔记(考研王道参考书)

操作系统目录 文章目录操作系统目录一、操作系统概述1.1 操作系统基本概念1.1.1 操作系统的概念、功能1.1.2 操作系统的特性1.1.3 操作系统的发展与分类1.1.4 操作系统的运行机制1.2 内核态与用户态1.3 中断、异常1.4 系统调用~~1.5 计算机的层次结构~~~~1.6 操作系统引导&…

【数学建模】常用算法-主成分分析PCA的Python实现

1前言 本文主要讲解主成分分析析法&#xff08;PCA&#xff09;的python实现&#xff0c;后续会跟进实例分析 2 原理-代码实现 2.1 实现步骤 主成分分析PCA是一种应用广泛的和降维方法&#xff0c;对其实现做以下归纳 2.2 代码实现 导入包 import numpy as np定义计算协…

VBA提高篇_12 文本文件逐行读取,输入输出操作自如

文章目录一、VBA打开文本文件的4步操作二、文本输入Excel示例三、 Excel输出文本示例3.1 Open For Output3.2 Open For Append3.3 Print 打印文件3.4 工作簿 循环工作表 循环输出文件四 、先输入后输出 Demo实战演练一、VBA打开文本文件的4步操作 打开文本文件 找到指定文件并调…

看这篇就够了——ubuntu扩展屏幕及装显卡驱动后黑屏问题

1.问题说明首先说明为什么外接屏幕需要装显卡驱动&#xff0c;显卡由GPU和显存构成&#xff0c;又称为显示适配器&#xff0c;与数据的输出有密切关系。在ubuntu系统中&#xff0c;外接显示器是用的独立显卡驱动&#xff0c;而内置屏幕用的是集显驱动,也正是因为调用显卡的不同…

Day01-数据分析图鉴

文章目录Day01-数据分析图鉴一、 数据分析是什么&#xff1f;二、为什么数据分析这么火&#xff1f;三、数据分析的行业现状如何&#xff1f;四、对工作经验要求高吗&#xff1f;五、工资情况六、学完数据分析的效果&#xff1f;1、Python可视化2、TableauDay01-数据分析图鉴 …

Maven打包时出现Process terminated错误

Maven打包时出现Process terminated错误检查maven的配置文件多引入了一次控制器编码错误切点表达式错误用maven打包时出现Process terminated样式的错误&#xff0c;报错如下&#xff1a;查看报错信息 检查maven的配置文件 多引入了一次控制器 注释掉多余的 编码错误 File e…