首页 > 其他分享 >【设计模式】模板方法模式Template Method:实现同一模板框架下的扩展

【设计模式】模板方法模式Template Method:实现同一模板框架下的扩展

时间:2023-09-16 16:09:38浏览次数:32  
标签:子类 void System Template println 设计模式 public 模板

(目录)

模板方法模式的原理和代码实现都比较简单,也被广泛应用,但是因为使用继承机制,副作用往往盖过了主要作用,所以在使用时尤其要小心谨慎。


原理

模板方法模式原始定义是:在操作中定义算法的框架,将一些步骤推迟到子类中。

模板方法让子类在不改变算法结构的情况下重新定义算法的某些步骤。

关键:解决算法框架这类特定的问题,同时明确表示需要使用继承的结构

UML 图: image.png

模板方法模式包含的关键角色有两个:

  1. **抽象父类:**定义一个算法所包含的所有步骤,并提供一些通用的方法逻辑;
  2. **具体子类:**继承自抽象父类,根据需要重写父类提供的算法步骤中的某些步骤。

UML 对应代码实现:

public abstract class AbstractClassTemplate {
    void step1(String key){
        //dosomthing
        System.out.println("=== 在模板类里 执行步骤 1");
        if (step2(key)) {
            step3();
        } else {
            step4();
        }
        step5();
    }
    boolean step2(String key){
        System.out.println("=== 在模板类里 执行步骤 2");
        if ("x".equals(key)) {
            return true;
        }
        return false;
    }
    abstract void step3();
    abstract void step4();
    void step5() {
        System.out.println("=== 在模板类里 执行步骤 5");
    }
    void run(String key){
        step1(key);
    }
}

public class ConcreteClassA extends AbstractClassTemplate {
    @Override
    void step3() {
        System.out.println("===在子类 A 中 执行:步骤3");
    }
    @Override
    void step4() {
        System.out.println("===在子类 A 中 执行:步骤4");
    }
}

public class ConcreteClassB extends AbstractClassTemplate {
    @Override
    void step3() {
        System.out.println("===在子类 B 中 执行:步骤3");
    }
    @Override
    void step4() {
        System.out.println("===在子类 B 中 执行:步骤4");
    }
}

public class Demo {
    public static void main(String[] args) {
        AbstractClassTemplate concreteClassA = new ConcreteClassA();
        concreteClassA.run("");
        System.out.println("===========");
        AbstractClassTemplate concreteClassB = new ConcreteClassB();
        concreteClassB.run("x");
    }
}
// 输出结果:
// === 在模板类里 执行步骤 1
// === 在模板类里 执行步骤 2
// ===在子类 A 中 执行:步骤4
// === 在模板类里 执行步骤 5
// ===========
// === 在模板类里 执行步骤 1
// === 在模板类里 执行步骤 2
// ===在子类 B 中 执行:步骤3
// === 在模板类里 执行步骤 5

模板方法模式的实现原理很简单,就是一个父类下面的子类通过继承父类而使用通用的逻辑,同时根据各自需要优化其中某些步骤。


使用模板方法的理由

使用模板方法模式的原因主要有两个:

  1. 期望在一个通用的算法或流程框架下进行自定义开发。 比如,在使用 Jenkins 的持续集成发布系统中,可以定制一个固定的 Jenkins Job 任务,将打包、发布、部署的流程作为一个通用的流程。对于不同的系统来说,只需要根据自身的需求增加步骤或删除步骤,就能轻松定制自己的持续发布流程;
  2. **避免同样的代码逻辑进行重复编码。**比如,当调用 HTTP 接口时,我们会使用 HttpClient、OkHttp 等工具类进行二次开发,不过我们经常会遇见这样的情况,明明有人已经封装过同样的功能,但还是忍不住想要对这些工具类进行二次封装开发,实际上最终实现的功能都只是调用 HTTP 接口,这样“重复造轮子”会非常浪费开发时间。这时如果使用模板方法模式定义个统一的调用 HTTP 接口的逻辑,就能很好地避免重复编码。

使用场景

模板方法模式的使用场景一般有:

  1. 多个类有相同的方法并且逻辑可以共用时;
  2. 将通用的算法或固定流程设计为模板,在每一个具体的子类中再继续优化算法步骤或流程步骤时;
  3. 重构超长代码时,发现某一个经常使用的公有方法

假设设计一个简单的持续集成发布系统——研发部开发的代码放在 GitLab 上,并使用一个固定的发布流程来进行程序的上线发布,使用模板方法模式来定义一系列规范的流程

  1. 首先,定义一个通用的流程框架类 DeployFlow,定义的步骤有六步,分别是:
  1. 从 GitLab 上拉取代码
  2. 编译打包
  3. 部署测试环境
  4. 测试
  5. 上传包到线上环境
  6. 启动程序

其中,从 GitLab 上拉取代码、编译打包、部署测试环境和测试这四个步骤,需要子类来进行实现,代码如下:

package cn.happymaya.ndp.template_method.example;

public abstract class DeployFlow {

    /* 使用final关键字来约束步骤不能轻易修改 */
    public final void buildFlow() {
        /* 从GitLab上拉取代码 */
        pullCodeFromGitlab();
        /* 编译打包 */
        compileAndPackage();
        /* 部署测试环境 */
        copyToTestServer();
        /* 测试 */
        testing();
        /* 上传包到线上环境 */
        copyToRemoteServer();
        /* 启动程序 */
        startApp();
    }

    public abstract void pullCodeFromGitlab();

    public abstract void compileAndPackage();

    public abstract void copyToTestServer();

    public abstract void testing();

    private void copyToRemoteServer() {
        System.out.println("统一自动上传 启动App包到对应线上服务器");
    }
    private void startApp() {
        System.out.println("统一自动 启动线上App");
    }
}

分别实现两个子类:

  1. 实现本地的打包编译和上传;
  2. 实现全自动化的持续集成式的发布。
/* 实现全自动化的持续集成式的发布 */
public class CicdDeployFlow extends DeployFlow {
    @Override
    public void pullCodeFromGitlab() {
        System.out.println("持续集成服务器将代码拉取到节点服务器上......");
    }
    @Override
    public void compileAndPackage() {
        System.out.println("自动进行编译&打包......");
    }
    @Override
    public void copyToTestServer() {
        System.out.println("自动将包拷贝到测试环境服务器......");
    }
    @Override
    public void testing() {
        System.out.println("执行自动化测试......");
    }
}

/* 实现本地的打包编译和上传 */
public class LocalDeployFlow extends DeployFlow{
    @Override
    public void pullCodeFromGitlab() {
        System.out.println("手动将代码拉取到本地电脑......");
    }
    @Override
    public void compileAndPackage() {
        System.out.println("在本地电脑上手动执行编译打包......");
    }
    @Override
    public void copyToTestServer() {
        System.out.println("手动通过 SSH 上传包到本地的测试服务......");
    }
    @Override
    public void testing() {
        System.out.println("执行手工测试......");
    }
}
  1. 运行一个单元测试,测试一下本地发布 LocalDeployFlow 和持续集成发布 CicdDeployFlow:
package cn.happymaya.ndp.template_method.example;

public class Client {
    public static void main(String[] args) {
        System.out.println("开始本地手动发布流程======");
        DeployFlow localDeployFlow = new LocalDeployFlow();
        localDeployFlow.buildFlow();
        System.out.println("********************");
        System.out.println("开始 CICD 发布流程======");

        DeployFlow cicdDeployFlow = new CicdDeployFlow();
        cicdDeployFlow.buildFlow();
    }
}
// 输出结果
// 开始本地手动发布流程======
// 手动将代码拉取到本地电脑......
// 在本地电脑上手动执行编译打包......
// 手动通过 SSH 上传包到本地的测试服务......
// 执行手工测试......
// 统一自动上传 启动App包到对应线上服务器
// 统一自动 启动线上App
// ********************
// 开始 CICD 发布流程======
// 持续集成服务器将代码拉取到节点服务器上......
// 自动进行编译&打包......
// 自动将包拷贝到测试环境服务器......
// 执行自动化测试......
// 统一自动上传 启动App包到对应线上服务器
// 统一自动 启动线上App

模板方法模式应用场景的最大特征在于,通常是对算法的特定步骤进行优化,而不是对整个算法进行修改。一旦整体的算法框架被定义完成,子类便无法进行直接修改,因为子类的使用场景直接受到了父类场景的影响。


优缺点

模板方法模式主要有以下两个优点:

  1. 有效去除重复代码。 模板方法模式的父类保存通用的代码逻辑,这样可以让子类不再需要重复处理公用逻辑,只用关注特定的逻辑,从而起到去除子类中重复代码的目的。
  2. 有助于找到更通用的模板。 由于子类间重复的代码逻辑都会被抽取到父类中,父类也就慢慢变成了更通用的模板,这样有助于积累更多通用的模板,提升代码复用性和扩展性。

模板方法模式的缺点:

  1. 不符合开闭原则。 一个父类调用子类实现操作,通过子类扩展增加新的行为,但是子类执行的结果便会受到父类的影响,不符合开闭原则的“对修改关闭”;
  2. 增加代码阅读的难度。 由于父类的某些步骤或方法被延迟到子类执行,那么需要跳转不同的子类阅读代码逻辑,如果子类的数量很多的话,跳转会很多,不方便联系上下文逻辑线索。而且模板方法中的步骤越多,其维护工作就可能会越困难。
  3. 违反里氏替换原则。 虽然模板方法模式中的父类会提供通用的实现方法,但是延迟到子类的操作便会变成某种定制化的操作,一旦替换子类,可能会导致父类不可用或整体逻辑发生变化。

标签:子类,void,System,Template,println,设计模式,public,模板
From: https://blog.51cto.com/panyujie/7493831

相关文章

  • 【设计模式】访问者模式Visitor:实现对象级别的矩阵结构
    (目录)访问者模式:一个原理看似很简单,但是理解起来有一定难度,使用场景相对较少的行为型模式:它能将算法与其所作⽤的对象隔离开来假如有这样⼀位⾮常希望赢得新客户的资深保险代理⼈。他可以拜访街区中的每栋楼,尝试向每个路⼈推销保险。所以,根据⼤楼内组织类型的不同,他可......
  • 【设计模式】解释器模式Interpreter Pattern:实现自定义配置规则功能
    解释器模式使用频率不算高,**通常用来描述如何构建一个简单“语言”的语法解释器。**它只在一些非常特定的领域被用到,比如:编译器;规则引擎;正则表达式;SQL解析等。不过,了解它的实现原理,可以帮助思考如何通过更简洁的规则来表示复杂的逻辑。模式原理分析解释器模式的原始......
  • 使用设计模式改写if/else或switch/case语句
    在写代码的时候,经常会用到if/else语句或者switch/case语句。虽然很省事,但是没有体现到java的封装、继承、多态等特性。没有用到java的面向对象编程的精髓。比如这种if/else语句:Stringstr="菠萝";if("苹果".equals(str)){ System.out.println("又大又红的苹果");}else......
  • 博客页面模板
    简单的博客页面模板,并将背景颜色设置为白色:<!DOCTYPEhtml><html><head><style>body{background-color:white;margin:20px;font-family:Arial,sans-serif;}h1{color:#333;}p{color:#555;......
  • 8.前端设计模式之混合模式
    MaxinPattern: Addfunctionalitytoobjectsorclasseswithoutinheritance在不使用的继承的情况下为对象或者类添加功能在JavaScript中混合也是通过原型链实现的。比如有个Dog类:classDog{constructor(name){this.name=name;}}现在我们希望为Dog类添加bark、......
  • Vue-模板语法
    一、模板语法 插值语法最后都渲染成了字符串html:<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><title>Title</title><scriptsrc="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jque......
  • 在工作流引擎设计领域,是否自动计算未来的处理人的设计模式有哪些?
    概述流程的第一个节点发送下去的时候,就要把以后所有节点的处理人计算出来,能清楚的知道每个节点都是那些人处理.计算未来处理人包括抄送节点、与待办节点.默认的模式为:每个节点发送的时候即使计算,就是不计算未来处理人.流程设计特征.流程的所有节点的接受人不能是主管选择的,只能......
  • 深入剖析模板引擎:理解原理、应用场景和常见类型
    模板引擎是一种广泛应用于Web开发的工具,能够将动态数据与静态模板进行结合,生成最终的页面内容。本篇博客将详细介绍模板引擎的原理、常见应用场景以及多种类型的模板引擎。引言模板引擎是现代Web开发中不可或缺的一部分,它的作用是将静态的模板文件与动态的数据进行结合,生成最终呈......
  • C#使用NPOI读取模板生成EXCEL
     C#使用NPOI读取模板生成EXCELstringcurrentDirectory=System.AppDomain.CurrentDomain.BaseDirectory;//读取Excel模板文件FileStreamfs=newFileStream(currentDirectory+"BoxPackinglist.xlsx",FileMode.Open,FileA......
  • flask从入门到精通之钩子、异常、context、jinjia模板、过滤器
    一、请求全局钩子【hook】此处的全局钩子,其实就是类似django里面的中间件。也就是只要调用或者注册了,在http请求响应中是必然执行的。在客户端和服务器交互的过程中,有些准备工作或扫尾工作需要处理,比如:在项目运行开始时,建立数据库连接,或创建连接池;在客户端请求开始时,根据......