一、模板方法模式

1、模板方法模式
模板方法模式(Template Method Pattern),又称为模板模式,它属于行为型设计模式。

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

模板方法模式实际上封装了一个固定流程,该流程有几个步骤组成,具体步骤可以由子类进行不同的实现。

2、模板方法模式的结构
模板方法模式需要注意抽象类与具体子类之间的协作。
它用到了虚函数的多态性技术以及“不用调用我,让我来调用你”的反向控制技术。

1. 模板方法模式包含以下主要角色:

(1)抽象类/抽象模板(Abstract Class)

抽象模板类,负责给出一个算法的轮廓和骨架。
它由一个模板方法和若干个基本方法构成。这些方法的定义如下:

  • 模板方法:定义了一套算法的骨架,按某种顺序调用其包含的基本方法。
  • 基本方法:是算法骨架/流程的某些步骤进行具体实现,包含以下几种类型,
    • 抽象方法:在抽象类中声明,由具体子类实现。
    • 具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
    • 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。

(2)具体子类/具体实现(Concrete Class)

具体实现类,实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。

2. 模板方法模式的结构图如下:(图来自网络)
在这里插入图片描述

3、优缺点
主要优点:

  • 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
  • 将相同的部分代码提取到抽象父类中,可以提高代码的复用性。
  • 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。

主要缺点:

  • 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,间接地增加了系统实现的复杂度。
  • 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
  • 由于继承关系自身的缺点,如果父类添加新的抽象方法,则所有子类都要改一遍。

4、使用场景

算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。
当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展。

注意事项:一般模板方法都加上 final 关键字,避免子类对其覆写,防止其恶意操作。

二、模式的通用实现

模板方法模式的代码如下:

public class TemplateMethodPattern {
    public static void main(String[] args) {
        AbstractClass tm = new ConcreteClass();
        tm.TemplateMethod();
    }
}

//抽象类
abstract class AbstractClass {
    //模板方法
    public final void TemplateMethod() {
        SpecificMethod();
        abstractMethod1();
        abstractMethod2();
    }

    //具体方法
    public void SpecificMethod() {
        System.out.println("抽象类中的具体方法被调用...");
    }

    //抽象方法1
    public abstract void abstractMethod1();

    //抽象方法2
    public abstract void abstractMethod2();
}

//具体子类
class ConcreteClass extends AbstractClass {
    @Override
    public void abstractMethod1() {
        System.out.println("抽象方法1的具体实现被调用...");
    }

    @Override
    public void abstractMethod2() {
        System.out.println("抽象方法2的具体实现被调用...");
    }
}

三、模式的应用实例

用一个做饭的例子来说明。简单地定义一下步骤(模板方法):

  1. 点火
  2. 固定食材和口味烹饪
  3. 关火

1、抽象父类

public abstract class AbstractCook {

    public final void doCook(){
        openFire();
        cooking();
        closedFire();
    }

    protected abstract void cooking();

    protected void openFire() {
        System.out.println("点火,开始做菜了");
    }
    protected void closedFire() {
        System.out.println("关火,菜出锅了");
    }
}

2、具体子类

public class Crayfish extends AbstractCook {
    @Override
    protected void cooking() {
        System.out.println("一份小龙虾!");
    }
}

public class FryRice extends AbstractCook {
    @Override
    protected void cooking() {
        System.out.println("一份炒饭!");
    }
}

3、测试

public class TemplateMethodPatternTest {
    public static void main(String[] args) {
        AbstractCook crayfish = new Crayfish();
        crayfish.doCook();
        System.out.println("===========");
        FryRice fryRice = new FryRice();
        fryRice.doCook();

    }
}

在这里插入图片描述

四、钩子方法的应用实例

钩子方法的主要目的:

目的是干预执行流程,正确使用钩子方法可以使得子类控制父类的行为。

做饭的例子改造下,给饭菜加上口味。简单地定义一下步骤(模板方法):

  1. 点火
  2. 根据食材和口味烹饪
  3. 关火

1、抽象父类

public abstract class AbstractCook {

    public final void doCook(){
        openFire();
        cooking();
        if(isTasteHookMethod()){
            taste();
        }
        closedFire();
    }

    //钩子方法1
    public boolean isTasteHookMethod() {
        return false;
    }
    protected void taste(){
    }

    protected abstract void cooking();

    protected void openFire() {
        System.out.println("点火,开始做菜了");
    }
    protected void closedFire() {
        System.out.println("关火,菜出锅了");
    }
}

2、具体子类

public class Crayfish extends AbstractCook {
    @Override
    protected void cooking() {
        System.out.println("一份小龙虾!");
    }

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

    @Override
    protected void taste() {
        System.out.println("麻辣味的,多放辣!");
    }
}

public class FryRice extends AbstractCook {
    @Override
    protected void cooking() {
        System.out.println("一份炒饭!");
    }
}

3、测试代码同上
在这里插入图片描述

模板方法模式在框架源码中使用也很广泛,比如:

  • JDK源码中 AbstractList抽象类
  • Mybatis源码中 BaseExecutor抽象类

—— Stay Hungry. Stay Foolish. 求知若饥,虚心若愚。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐