模版模式:优雅、灵活地拓展你的pipeline

525 阅读5分钟

模版方法,作为一种行为型模式,通过在抽象类或接口中定义一个操作中的算法骨架,而将一些步骤具体执行延迟到子类中实现,从而使得父类的方法执行可以获得不一样的结果。从而达到了代码复用、扩展性好、灵活度高的设计目的。

使用时机

模版方法使用时机,主要是相同、相似方法使用较多的情况。采用模版方法,可以将这些相似的方法提取出来,制定出一个相对普适的模版,从而减少代码的重复书写,提高代码的复用率。抽象化的使用场景及实现逻辑如下图所示。

image-20220327125629386.png

基本定义

在了解如何写模版方法之前,我们有必要来了解一些有关模版方法的关键名词及术语。

基本方法:

基本方法又可以划分为如下三类:

1、具体方法(Concrete Method)

​ 具体方法指的是由父类/接口负责实现的方法,子类不可以对该方法进行更改。

2、抽象方法(Abstract Method)

​ 抽象方法则指的是不由父类/接口实现具体步骤,而是将其具体操作步骤推迟到子类实现,从而对不同情况实现不同的操作。

3、钩子方法(Hook Method)

​ 一个钩子方法由抽象类声明并实现,而子类会加以扩展。它是子类可以选择性实现或不实现的方法,通常抽象类给出是一个空实现,作为方法的默认实现。

​ 这样的默认实现,被称为默认钩子方法。这种空钩子方法也叫做“Do Nothing Hook”。默认钩子方法在缺省适配模式也有相应的使用。缺省适配模式讲的是一个类为接口提供一个默认的空实现,从而使得子类不必给出所有方法的实现,因为通常一个具体类并不需要所有的方法。这个思想正好同默认钩子方法不谋而合。

​ 钩子方法常见的用途为,将两个存在不同调用关系的pipeLine流程,通过钩子方法联系到同一个模版中,从而屏蔽不同内容的差异性。但是,需要注意的一点是,**钩子方法的名字应当以do开始,这是熟悉设计模式的Java开发人员的标准做法。**如doScan、doGet等。

模版方法:

​ 模版方法是模版模式的核心点,其是定义在抽象类中的,是把基本操作方法组合在一起形成总的算法或行为的方法。一个抽象类可以有任意多个模板方法,而不限于一个。每一个模板方法都可以调用任意多个具体方法。原则上,子类不可以、也不应该对模版方法进行覆盖或者重写

上述定义中各类方法的对应模块大致如下图所示:

image-20220327133826087.png

代码实践

模版方法的实现,在java中有两种方式,一种是基于抽象类的实现方式,另外一种则是基于接口的实现方式

这里我们以抽象类的实现方法为例子介绍相应的模版方法的实现。

public abstract class Template {

    public void concreteMethod() {
        System.out.println("concreteMethod:");
    }

    public abstract void abstractMethod();

    public void hookMethod(){
        System.out.println("hookMethod:");
        System.out.println("实现默认hookMethod!");
    }


    public final void execute() {
        concreteMethod();

        abstractMethod();

        hookMethod();
    }
}

首先,定义出我们的模版接口,其中包含三个方法concreteMethod、abstractMethod、hookMethod。就分别对应于我们上述提到的具体方法、抽象方法及钩子方法

然后在execute()方法内,定义好三个方法的基本执行方法,同时采用final修饰符,使得子类无法修改execute方法中的执行顺序。

然后在我们的子类HerFuction中,首先必须要对抽象方法进行相应的实现。这里我们简单的输出类名。

而在钩子方法hookMethod中,我们则对原方法进行增强,多输出一句话:“我还要执行自己的hookMethod方法!”。

public class HerFunction implements Template {

    @Override
    public void abstractMethod() {
        System.out.println("HerFunciton !");
    }

    @Override
    public void hookMethod() {
        Template.super.hookMethod();
        System.out.println("我还要执行自己的hookMethod方法!");
    }
}

类似地,在第二个子类MyFunction中,我们也需要实现相应的抽象类方法。但对于钩子方法则采用默认的抽象类中的方法实现即可。

public class MyFunction extends Template {

    @Override
    public void abstractMethod() {
        System.out.println("My Function!");
    }
}

最后,在启动方法中分别创建MyFunction、HerFunction对应的对象,并调用父类的execute方法即可。

    public static void main(String[] args) {
        Template myFunction = new MyFunction();
        myFunction.execute();
        System.out.println("================我是分割线=========================");
        Template herFunction = new HerFunction();
        herFunction.execute();
    }

最后得到的结果如下:

image-20220327145635211.png

可以看到,子类对concreteMethod方法都成功实现了复用,而对于abstractMethod则根据不同子类实现了不同的逻辑,体现了差异性。同时钩子方法的默认实现及子类实现,也体现了模版的灵活性。

优缺点分析

优点:

1、利用模板方法将相同处理逻辑的代码放到抽象父类中,可以提高代码的复用性。

2、将相同业务含义的处理代码放置到不同的子类中,通过对子类的扩展增加新的行为,从而提高代码的扩展性。

3、把不变的行为写在父类上,去除子类的重复代码,提供了一个很好的代码复用平台,符合开闭原则。

缺点:

1、模版方法的实现依赖于子类的构建,因此类的数量会存在明显的增加,增加了类的复杂度。

2、采用抽象类情况下,继承关系自身存在一定缺点,如果父类添加新的抽象方法,所有子类都要对该抽象方法进行实现。JAVA语言可以采用接口+default关键字的方式,一定程度上避免这个修改。(但是带来的副作用是不能采用final对方法进行限制)

参考文献

《JAVA设计模式》之模板模式(Template)

模版模式

详解Java8接口中引入default关键字的本质原因

模板模式的优缺点