模板方法模式

535 阅读4分钟

前言

模板方法模式是行为型设计模式,原理和代码实现都比较简单。

目录

一、定义

定义一个操作中的算法的框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

一个父类下面的子类通过继承父类而使用通用的逻辑,同时根据各自需要优化其中某些步骤一个父类下面的子类通过继承父类而使用通用的逻辑,同时根据各自需要优化其中某些步骤

二、模式原理分析

举个拉钩教育上的例子,设计一个简单的持续集成发布系统,研发部开发的代码放在GitLab上,并使用一个固定的发布流程来进行程序的上线发布。

public abstract class DeployFlow {

    //使用final关键字来约束步骤不能轻易修改

    public final void buildFlow() {
        pullCodeFromGitlab(); //从GitLab上拉取代码
        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 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("执行手工测试......");
    }

}

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 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

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

三、使用场景

  • 多个子类有公有的方法,并且逻辑基本相同时
  • 重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个子类实现
  • 重构时,模板方法是一个经常使用的模式,把相同代码抽取到父类中,然后通过"钩子函数"约束其行为

四、优点

  • 有效去除重复代码
  • 封装不变部分,扩展可变部分
  • 提取公共部分代码,便于维护
  • 行为由父类控制,子类实现

五、缺点

  • 不符合开闭原则。 一个父类调用子类实现操作,通过子类扩展增加新的行为,但是子类执行的结果便会受到父类的影响,不符合开闭原则的“对修改关闭”。

  • 增加代码阅读的难度。 由于父类的某些步骤或方法被延迟到子类执行,那么需要跳转不同的子类阅读代码逻辑,如果子类的数量很多的话,跳转会很多,不方便联系上下文逻辑线索。

  • 违反里氏替换原则。 虽然模板方法模式中的父类会提供通用的实现方法,但是延迟到子类的操作便会变成某种定制化的操作,一旦替换子类,可能会导致父类不可用或整体逻辑发生变化。