设计模式解析之模板方法模式:设计灵活可扩展的算法框架

news2024/11/25 5:48:25

目录

  • 1. 引言
  • 2. 概要
    • 2.1 概念
    • 2.2 结构
    • 2.3 类图
    • 2.4 工作流程
  • 3. 应用场景
    • 3.1 适用情况:
    • 3.2 常见例子:
  • 4. 代码衍化过程
    • 初版:甲乙学生都抄试卷
    • 第二版:提炼代码
    • 第三版:抽象出算法骨架
    • 第四版:模板方法
    • 变化过程总结及未来展望
  • 5. 总结

1. 引言

    在软件开发中,设计和实现算法是一项常见的任务。然而,随着需求的变化和代码的增长,算法的复杂性往往会导致代码变得冗长、难以维护和重复编写。这时,模板方法模式就成为了一个解放程序员双手的利器。模版方法是一种常见的设计模式,它帮助我们定义一个算法的骨架,将具体实现交给子类去完成。本文将介绍模版方法的概念、应用场景以及如何使用,还会做一部分延申讲解。

2. 概要

2.1 概念

    模版方法是一种行为型设计模式,它定义了一个操作的算法骨架,具体步骤由子类实现。模版方法使得子类可以在不改变算法结构的情况下重新定义算法的某些步骤。

概念剖析:

    行为型设计模式:是一种软件设计模式,它关注对象之间的交互和通信,以及如何将责任分配给不同的对象。行为型设计模式描述了对象之间的相互作用和通信方式,使得系统中的对象能够更好地协调合作。

    行为型设计模式解决了不同对象之间的交互问题,提供了一些常见的设计方案,以便开发人员在特定的情况下作出选择。这些模式强调对象之间的关系、算法的分配和职责的分离,从而增强了代码的灵活性、可维护性和可复用性。

    模板方法基于继承机制,让子类决定如何实现算法中的一些步骤,从而使得不同的子类可以有不同的行为。
模板方法模式通过把不变部分的算法封装在父类中,而将变化部分的实现留给子类实现,从而提高了代码的复用性和可维护性。在模板方法模式中,父类决定了算法的执行顺序和一些通用步骤,而子类可以自行实现这些步骤的具体细节,以达到特定的效果。

    模板方法模式常用于框架设计中。框架中定义了一系列的算法骨架,具体算法的实现由各个子类来完成。这样,框架具有了很好的扩展性,可以轻松地添加新的算法或修改已有算法的实现,同时也使得框架内的代码更加稳定和可靠。

    总之,模板方法模式体现了一种抽象思想,即将具有相同的算法骨架的一组算法封装到一个抽象类中,并将一些具体的实现步骤留给子类去完成。由于其抽象性和灵活性,模板方法模式在面向对象软件开发中得到了广泛的应用。

    算法骨架:可以理解为算法的步骤。在模板方法设计模式中,抽象类定义了算法的整体结构和执行顺序,也就是算法的骨架,包括了一系列步骤或操作。这些步骤通常是按照特定的顺序进行执行,但具体的实现可能有所不同。

    模板方法中的算法骨架由抽象方法和具体方法组成。抽象方法是需要子类提供具体实现的步骤,而具体方法是已经实现的通用步骤或可以被子类重写的可选步骤。子类通过继承抽象类并实现其中的抽象方法,从而完成对算法骨架的填充,使得整个算法能够根据子类的实际需求得到具体执行。

    总之,算法骨架可以看作是算法的步骤和执行顺序的抽象描述,通过模板方法设计模式可以更好地实现算法的复用和扩展。

2.2 结构

模版方法模式包含以下几个角色:

    1、抽象类(Abstract Class):定义了算法骨架和抽象方法,包含一系列的具体步骤,并且可以包含一些默认的实现,用于规范子类的行为。抽象类中可能还包含其他抽象方法或具体方法,这些方法可以被模板方法调用。

    2、具体类(Concrete Class):具体类是抽象类的子类,负责实现在抽象类中定义的抽象方法,完成特定的具体步骤。具体类还可以覆盖抽象类中的钩子方法,以便在必要时对算法进行扩展或修改。

    3、钩子方法(Hook Method):钩子方法是在抽象类中定义但没有具体实现的方法,它可以被具体类覆盖,用于控制算法的执行。钩子方法提供了一个扩展点,使得具体类可以在不修改模板方法的情况下,对算法进行个性化的定制。
    钩子方法通常在抽象类中被定义为虚方法(即提供默认实现,但可以被具体类覆盖)或者抽象方法(即没有具体实现,需要由具体类来实现)。这样,在抽象类的模板方法中通过调用钩子方法,可以提供一个扩展点,具体类可以选择性地覆盖钩子方法,以实现个性化的定制。如果具体类不覆盖钩子方法,将使用抽象类中定义的默认实现。

    4、模板方法(Template Method):模板方法是抽象类中定义的核心方法,它规定了算法的骨架和执行顺序,而具体的步骤则由具体类来实现。模板方法通过调用抽象类中定义的具体方法、抽象方法和钩子方法,完成算法的整体流程。

    在模板方法模式中,抽象类起到了定义算法骨架和提供公共方法的作用,具体类则负责实现算法的具体细节。通过将变化部分封装在具体类中,模板方法模式实现了算法的复用与扩展。这样,当需要修改或添加新的算法时,只需创建新的具体类,并在其中实现具体步骤即可,而不需要修改抽象类和其他具体类的代码。这样的设计能够提高代码的灵活性、可维护性和可扩展性。

2.3 类图

在这里插入图片描述
类图和上面的角色进行对应:
1:AbstractClass 抽象类
2:Concrete Class 具体类
3:primitiveOperation 1和primitiveOperation 2 钩子方法
4:templateMethod 模板方法

2.4 工作流程

模版方法遵循以下工作流程:

1、定义抽象类,声明模版方法和抽象方法。
2、实现抽象类,提供模版方法的具体实现,其中某些步骤使用抽象方法。
3、派生具体类,继承抽象类并实现抽象方法。

3. 应用场景

3.1 适用情况:

    在一个算法的框架下,允许子类根据需要扩展或修改某些步骤。
    多个相关的类拥有相似的行为,可以使用模版方法将公共操作提取到抽象类中。

3.2 常见例子:

    1、数据库访问框架:在数据库访问框架中,可以使用模板方法模式定义一个通用的数据访问流程。抽象类中的模板方法定义了打开数据库连接、执行SQL语句、处理结果集等具体步骤。具体类可以继承该抽象类并实现具体数据库的访问细节,如MySQL、Oracle等,从而实现针对不同数据库的操作。

    2、游戏开发中的角色AI:在游戏开发中,角色AI往往需要遵循一定的行为模式。可以使用模板方法模式定义一个抽象类,其中的模板方法描述了角色的基本行为流程,比如巡逻、攻击、逃跑等。具体角色类可以继承该抽象类并实现具体的行为细节,以适应不同类型的角色AI。

    3、咖啡和茶的冲泡过程:冲泡咖啡和茶的过程有许多共同的步骤,比如烧水、加入咖啡或茶叶、搅拌等。可以使用模板方法模式定义一个饮料冲泡的抽象类,其中的模板方法描述了整个冲泡的流程。具体子类可以分别实现咖啡和茶的具体冲泡细节,从而创建不同类型的饮料。

    4、操作系统中的进程调度算法:在操作系统的进程调度中,有各种调度算法,比如先来先服务(FCFS)、时间片轮转等。可以使用模板方法模式定义一个抽象类,其中的模板方法描述了进程调度的基本流程,包括进程就绪队列的管理、时间片分配等。具体子类可以继承该抽象类并实现具体的调度算法细节,以适应不同的调度策略。

4. 代码衍化过程

业务背景:每个人都要抄一样的考卷,然后再将自己的答案写上

初版:甲乙学生都抄试卷

//甲抄的试卷
public class TestPaperA {
    public void testQuestion1() {
        System.out.println("试题1题目内容");
        System.out.println("答案:b");
    } public void testQuestion2() {
        System.out.println("试题2题目内容");
        System.out.println("答案:c");
    } public void testQuestion3() {
        System.out.println("试题3题目内容");
        System.out.println("答案:b");
    }
}

//乙抄的试卷
public class TestPaperB {
    public void testQuestion1() {
        System.out.println("试题1题目内容");
        System.out.println("答案:a");
    } public void testQuestion2() {
        System.out.println("试题2题目内容");
        System.out.println("答案:b");
    } public void testQuestion3() {
        System.out.println("试题3题目内容");
        System.out.println("答案:b");
    }
}

//客户端
public class Client {
    public static void main(String[] args) {
        System.out.println("写生甲抄的试卷:");
        TestPaperA studentA=new TestPaperA();
        studentA.testQuestion1();
        studentA.testQuestion2();
        studentA.testQuestion3();
        System.out.println("----------------------------------------");
        System.out.println("写生乙抄的试卷:");
        TestPaperA studentB=new TestPaperA();
        studentB.testQuestion1();
        studentB.testQuestion2();
        studentB.testQuestion3();
    }
}

    从上面的代码中可以看出学生甲和学生乙分别抄了试卷(两个类),试卷类非常类似,除了答案不同。
    这样的代码,容易错,还非常难以维护

第二版:提炼代码

//试卷父类
public class TestPaper {
    public void testQuestion1() {
        System.out.println("试题1题目内容");

    } public void testQuestion2() {
        System.out.println("试题2题目内容");

    } public void testQuestion3() {
        System.out.println("试题3题目内容");

    }
}

//甲的答卷
public class TestPaperA extends TestPaper {
    public void testQuestion1() {
       super.testQuestion1();
        System.out.println("答案:b");
    } public void testQuestion2() {
        super.testQuestion1();
        System.out.println("答案:c");
    } public void testQuestion3() {
        super.testQuestion3();
        System.out.println("答案:b");
    }
}

//乙的答卷
public class TestPaperB extends TestPaper {
    public void testQuestion1() {
       super.testQuestion1();
        System.out.println("答案:b");
    } public void testQuestion2() {
        super.testQuestion1();
        System.out.println("答案:a");
    } public void testQuestion3() {
        super.testQuestion3();
        System.out.println("答案:b");
    }
}

//客户端
public class Client {
    public static void main(String[] args) {
        System.out.println("写生甲的试卷:");
        designpatterns.template.testPaper.TestPaperA studentA=new designpatterns.template.testPaper.TestPaperA();
        studentA.testQuestion1();
        studentA.testQuestion2();
        studentA.testQuestion3();
        System.out.println("----------------------------------------");
        System.out.println("写生乙的试卷:");
        designpatterns.template.testPaper.TestPaperA studentB=new TestPaperA();
        studentB.testQuestion1();
        studentB.testQuestion2();
        studentB.testQuestion3();
    }
}


    上面的代码中将试题抽取出来作为父类,甲乙同学只需要写上答案即可。
    但是甲和乙的还是有很多重复的操作,既然用了继承,就应该把所有重复代码上升到父类

第三版:抽象出算法骨架

//试卷父类
abstract class TestPaper {
    public void testQuestion1() {
        System.out.println("试题1题目内容");
        System.out.println("答案是:"+this.answer1());

    }
    protected abstract String answer1();


    public void testQuestion2() {
        System.out.println("试题2题目内容");
        System.out.println("答案是:"+this.answer2());
    }
    protected abstract String answer2();

    public void testQuestion3() {
        System.out.println("试题3题目内容");
        System.out.println("答案是:"+this.answer3());
    }
    protected abstract String answer3();
}

//甲的答卷
public class TestPaperA extends TestPaper{

    @Override
    protected String answer1() {
        return "b";
    }

    @Override
    protected String answer2() {
        return "a";
    }

    @Override
    protected String answer3() {
        return "b";
    }
}

//乙的答卷
public class TestPaperB extends TestPaper{

    @Override
    protected String answer1() {
        return "a";
    }

    @Override
    protected String answer2() {
        return "a";
    }

    @Override
    protected String answer3() {
        return "b";
    }
}

//客户端
public class Client {
    public static void main(String[] args) {
        System.out.println("学生甲的试卷:");
        TestPaper studentA=new TestPaperA();
        studentA.testQuestion1();
        studentA.testQuestion2();
        studentA.testQuestion3();
        System.out.println("_______________");
        System.out.println("学生甲的试卷:");
        TestPaper studentB=new TestPaperA();
        studentB.testQuestion1();
        studentB.testQuestion2();
        studentB.testQuestion3();
    }
}
 

    这一版代码将共同的部分放在父类中的模板方法中,父类中定义一个算法的骨架,具体实现交给子类的方法来完成。
    但是这一版中的客户端中仍然存在很多重复的代码

第四版:模板方法

//模板
abstract class AbstractClass {
    public void templateMethod(){
        //写一些可以被子类共享的代码

        //调用
        this.primitiveOperation1();
        this.primitiveOperation2();
    }
    public abstract  void primitiveOperation1();//子类个性的行为,放到子类去实现
    public abstract  void primitiveOperation2();

}

//具体板式A
public class ConcreteClassA extends AbstractClass{

    @Override
    public void primitiveOperation1() {
        System.out.println("具体类A方法1实现");
    }

    @Override
    public void primitiveOperation2() {
        System.out.println("具体类A方法2实现");
    }
}

//具体板式B
public class ConcreteClassB extends AbstractClass{

    @Override
    public void primitiveOperation1() {
        System.out.println("具体类B方法1实现");
    }

    @Override
    public void primitiveOperation2() {
        System.out.println("具体类B方法2实现");
    }
}

//客户端
public class Client {
    public static void main(String[] args) {
        AbstractClass concreteClass;
        concreteClass = new ConcreteClassA();
        concreteClass.templateMethod();


        concreteClass = new ConcreteClassB();
        concreteClass.templateMethod();

    }
}


输出结果
在这里插入图片描述
NS图
在这里插入图片描述
在这里插入图片描述

变化过程总结及未来展望

1、抽象题:把题目要求抽出来 TestPaper类(这里属于抽出相同部分)
2、抽答案:把每个人的答案抽取出来,上面例子中的answer方法 (这里属于抽出不同部分)
3、抽步骤:也就是算法骨架抽出来,还有钩子方法(抽相同和不同)
4、方法个数一样就可以抽象:这种想法的来源于上面例子的第四版,primitiveOperation方法里面只有一句打印,说明内容你可以随便定义,方法起一样的名字,实现完全不一样。进而抽象为只要方法的个数一样时也可以考虑用到模板方法。
5、方法个数不一样,3个4个,就把3个抽出来:这一点就是更抽象了,即便方法个数不一样,也可以抽取其中一样个数的方法使用模板方法来实现。

    4和5是非常抽象了,所以需要想象力和创造力,把不一样的看成一样的。这也就是不是山也是山的过程。

5. 总结

    模版方法是一种非常有用的设计模式,它通过将算法的具体步骤交给子类实现,提供了代码复用和灵活性。通过抽象类和具体类的组织方式,模版方法使得代码更易于扩展和维护。在软件开发中,我们可以根据具体情况选择使用模版方法来解决问题。

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

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

相关文章

视频转gif的几个方法

视频转gif的软件,视频转gif的几个方法~什么是gif图?其实简单一点来说就是一张会动的图片,gif既有图片的属性,也具有视频的特点,因为严格来说gif属于一直图片文件的特殊,但是它的展现方式又和视频非常的像。…

【计算机视觉 | 图像模型】常见的计算机视觉 image model(CNNs Transformers) 的介绍合集(二)

文章目录 一、SqueezeNet二、Inception-v3三、Visual Geometry Group 19 Layer CNN四、MobileNetV1五、Data-efficient Image Transformer六、MobileNetV3七、self-DIstillation with NO labels(DINO)八、MLP-Mixer九、WideResNet十、Darknet-19十一、HR…

普通项目(java项目)使用lombok报错

java: JPS incremental annotation processing is disabled. Compilation results on partial recompilation may be inaccurate. Use build process "jps.track.ap.dependencies" VM flag to enable/disable incremental annotation processing environment. 解决方…

【ROS】机器人使用Nomachine进行远程控制

官网:NoMachine - Free Remote Desktop for Everybody 支持的系统: WindowsMacLinux 树莓派其他ARM板IOSAndroid 由于网速问题,可以使用我下载好的:(8.8.1_1) 链接:https://pan.baidu.com/s/…

CSS 修改el-calendar的样式,自定义样式

需求&#xff1a;自定义elementui的日历的样式&#xff1b;给符合条件的时间展示红点。 elementui的原始样式&#xff1a; 目标样式&#xff1a; 代码实现&#xff1a; html: <el-calendar v-model"calendarValue"><template slot"dateCell" …

AI人工智能时代: 大一新生如何入手IT行业?

AI人工智能时代: 大一新生如何入手IT行业? AI人工智能时代: 大一新生如何入手IT行业?引言1. 了解IT行业的基础1.1 什么是IT&#xff1f;1.2 AI是如何改变IT行业的&#xff1f; 2. 如何选择合适的编程语言2.1 常见的编程语言有哪些&#xff1f;2.2 如何选择&#xff1f; 3. 制…

OPCEnum作用OPC常见通讯问题

目录 1 OPCEnum作用 1.1 检索server列表 1.2 检索失败原因 1.3 是否可以没有OPCEnum 2 OPC通讯常见问题 2.1 不能浏览OPCServer列表 2.2不能连接远程OPC Server 2.3 数据显示Bad 2.4 OPC通讯错误码 1 OPCEnum作用 你知道OPCEnum有什么用吗&#xff1f;看完本文你就对O…

Web安全(文件上传的漏洞)

文件上传漏洞和危害 文件上传漏洞是指网络攻击者上传了一个可执行的文件到服务器上&#xff0c;当开发者没有对该文件进行合理的校验及处理的时候&#xff0c;很有可能让程序执行这个上传文件导致安全漏洞。大部分网站都会有文件上传的功能&#xff0c;例如头像、图片、视频等…

如何从Docker镜像中提取恶意文件

当发生容器安全事件时&#xff0c;需要从容器或镜像中提取恶意文件进行分析和处理。 本文主要介绍3种常见的方法&#xff1a; (1) 从运行的容器中复制文件 首先&#xff0c;需要从镜像运行启动一个容器&#xff0c;然后&#xff0c;使用docker cp命令从容器中提取文件到宿主机。…

Flutter——启动页白屏的优化

flutter启动页白屏的优化&#xff1a;使用图片替代白屏 结构图 核心的代码如上图&#xff0c;修改两个launch_background.xml里的代码为&#xff1a; <item><bitmapandroid:gravity"center"android:src"mipmap/ic_launcher" /></item>…

如何使用极狐GitLab 支持 ISO 27001 合规

目录 组织控制 技术控制 了解更多 本文来源&#xff1a;about.gitlab.com 作者&#xff1a;Joseph Longo 译者&#xff1a;武让 极狐GitLab 高级解决方案架构师 作为一体化平台&#xff0c;通过极狐GitLab 可以很容易实现 DevSecOps 全生命周期管理。极狐GitLab 使开发人员能…

numpy笔记:广播应用:实现一个对比学习的loss function

论文笔记&#xff1a;SUPERVISED CONTRASTIVE REGRESSION_UQI-LIUWJ的博客-CSDN博客的loss function 出于简单考虑&#xff0c;我们令vy&#xff0c;同时sim就是两个向量的内积&#xff0c;d是两个向量逐元素差的绝对值之和 1 数据 import numpy as npanp.array([[1,3,4],[2,…

点评项目核心内容

目录 拦截器设置 集群的session共享问题 基于redis实现共享session登录 创建bean对象技巧 什么是缓存 使用缓存来处理对象 使用String类型缓存来处理集合 缓存更新策略 主动更新策略 缓存穿透 空串""和null的区别 缓存null值解决穿透问题 缓存雪崩 缓存击穿…

二刷力扣--字符串

字符串 摘自Python文档-标准库&#xff1a; 在Python中&#xff0c; 字符串是由 Unicode 码位构成的不可变序列。 由于不存在单独的“字符”类型&#xff0c;对字符串做索引操作将产生一个长度为 1 的字符串。 也就是说&#xff0c;对于一个非空字符串 s, s[0] s[0:1]。 不存…

震惊!idea里斗地主还不算我还要玩大富翁!vscode也安排一个

1. 介绍 本来呢前段时间大约是三四月份吧&#xff0c;摸鱼的时候找到了这个插件 xechat-idea, 可以在idea里边摸鱼&#xff0c;斗地主&#xff0c;聊天等,项目地址为 https://github.com/anlingyi/xechat-idea , QQ群号754126966 然后我尝试着开发了两个游戏 一个是大富翁 一个…

Kubernetes(K8S)集群部署

目录 一、创建3台虚拟机 二、为每台虚拟机安装Docker 三、安装kubelet 3.1 安装要求 3.2 为每台服务器完成前置设置 3.3 为每台服务器安装kubelet、kubeadm、kubectl 四、使用kubeadm引导集群 4.1 master服务器 4.2 node1、node2服务器 4.3 初始化主节点 4.4 work节…

使用内网端口映射方案,轻松实现U8用友ERP的本地部署异地远程访问——“cpolar内网穿透”

文章目录 前言1. 服务器本机安装U8并调试设置2. 用友U8借助cpolar实现企业远程办公2.1 在被控端电脑上&#xff0c;点击开始菜单栏&#xff0c;打开设置——系统2.2 找到远程桌面2.3 启用远程桌面 3. 安装cpolar内网穿透3.1 注册cpolar账号3.2 下载cpolar客户端 4. 获取远程桌面…

元宇宙发展下的虚拟时尚

过去两年元宇宙概念大热&#xff0c;在元宇宙的发展过程中&#xff0c;时尚已逐渐成为其最受欢迎的应用领域之一。元宇宙中的时尚应用经历长期的发展&#xff0c;现已成为其最流行的用途之一。自2006年首届虚拟时装秀在Second Life举办以来&#xff0c;该行业的发展呈指数级增长…

【实训项目】滴滴电竞APP

1.设计摘要 2013年国家体育总局决定成立一支由17人组成的电子竞技国家队&#xff0c;第四届亚室会中国电竞代表队 出战第四届亚洲室内和武道运动会。 2014年1月13日CCTV5《体育人间》播放英雄联盟皇族战队的纪录片。 在2015到2019年间&#xff0c;我国电竞战队取得的无数值得…

气膜建筑凭借自身优势,提供多功能室内体育馆

气膜安装简单快捷&#xff0c;可作为临时性或季节性建筑&#xff0c;并且可以和其他常规结构建筑相连接。连接处均可进行气密性设计。功能分区明显&#xff0c;避免其他活动对运动的影响。方便运动与观光的有机结合。气膜建筑基础处理简单&#xff0c;迁移的损耗非常小&#xf…