什么是模板模式

模板模式(Template Pattern),有的地方也称为模板方法模式(Template Method Pattern),是一种在父类中定义程序执行规则,在子类中实现具体处理逻辑的设计模式。
在模板模式中,父类负责制定一套通用的程序执行流程,子类严格遵守这套流程,流程中包含多个抽象步骤,而每个步骤的具体实现则依赖于每个具体的子类。

UML类图

模板模式UML类图如下:
TemplatePattern.png
其中,父类AbsTemplate定义了模板方法templateMethod(),模板方法用来制定程序的执行流程,这套流程是固定的,不可被子类重写,在templateMethod()的实现中依次调用了抽象方法method1()method2()method3(),即完整的程序执行流程包含了method1()method2()method3()3个步骤,而method1()method2()method3()是抽象的,并未具体实现,具体实现交给了子类ConcreteImplAConcreteImplB

示例

知道了模板模式的概念和框架结构,我们对模板模式已经有了一个初步认识,接下来,我们以动物为例来演示模板模式,以进一步加深我们的理解。
动物是一个泛指的概念,其下包含许多具体种类,如鸟类、鱼类、兽类等,不同种类的动物在外观、习性方面存在差异,但也存在很多共性,例如动物都要吃东西、活动和休息,只是不同种类动物的食物偏好、活动形式和休息方式可能不一样,我们刚好可以以此来演示模板模式。
首先,我们抽象出动物父类AbsAnimal,并把动物的生活(live)定义为一个模板,在这个模板里包含了生活的一些基本环节,例如吃东西(eat)、活动(move)和睡觉(sleep),因此,我们可以定义一个不可变的live()方法,同时抽象出eat()move()sleep()3个抽象方法,然后在live()中依次调用eat()move()sleep(),这样,一个关于动物生活的模板就定义好了。AbsAnimal代码如下:

package com.xy.designpatterns.template.sample

/**
 * 动物抽象类
 */
abstract class AbsAnimal {

    /**
     * 模板方法
     * 表示动物每天的生活
     */
    fun live() {
        eat()
        move()
        sleep()
    }

    /**
     * 吃东西
     */
    abstract fun eat()

    /**
     * 活动
     */
    abstract fun move()

    /**
     * 睡觉
     */
    abstract fun sleep()
}

AbsAnimal仅表示抽象的动物,抽象的动物是不具体的,只知道要做哪些事情,而不明确每件事情该怎么去做,所以我们还需要解决表达“如何做”的问题,怎么解决呢?我们需要定义具体的动物类,即继承AbsAnimal的子类,子类通过重写AbsAnimal中的抽象方法,来说明每种动物具体是怎么吃东西的、怎么活动的、怎么睡觉的。在这里,我们定义3个具体的动物类:BirdFishLion,继承自AbsAnimal,并实现AbsAnimal中的抽象方法,代码如下:
Bird

package com.xy.designpatterns.template.sample

/**
 * 小鸟
 */
class Bird : AbsAnimal() {

    override fun eat() {
        println("小鸟吃昆虫")
    }

    override fun move() {
        println("小鸟在天空飞翔")
    }

    override fun sleep() {
        println("小鸟在树上的鸟窝里面睡觉")
    }
}

Fish

package com.xy.designpatterns.template.sample

/**
 * 小鱼
 */
class Fish : AbsAnimal() {

    override fun eat() {
        println("小鱼吃水草")
    }

    override fun move() {
        println("小鱼在水里游动")
    }

    override fun sleep() {
        println("小鱼钻进沙里睡觉")
    }
}

Lion

package com.xy.designpatterns.template.sample

/**
 * 狮子
 */
class Lion : AbsAnimal() {

    override fun eat() {
        println("狮子吃羚羊")
    }

    override fun move() {
        println("狮子在草原奔跑")
    }

    override fun sleep() {
        println("狮子趴在树下睡觉")
    }
}

这样,BirdFishLion3个具体动物类,便在AbsAnimal定义好的大的模板框架内,实现了对吃东西、活动、睡觉的自定义。
接下来,我们通过测试代码来看看示例代码的运行效果。
模板模式场景模拟器TemplatePatternSceneSimulator代码如下:

package com.xy.designpatterns.template

import com.xy.designpatterns.ISceneSimulator
import com.xy.designpatterns.template.sample.AbsAnimal
import com.xy.designpatterns.template.sample.Bird
import com.xy.designpatterns.template.sample.Fish
import com.xy.designpatterns.template.sample.Lion

/**
 * 模板模式场景模拟器
 */
class TemplatePatternSceneSimulator : ISceneSimulator {

    override fun run() {
        println("============模板模式示例============")
        println("Bird的生活:")
        val bird: AbsAnimal = Bird()
        bird.live()
        println("====================================")
        println("Fish的生活:")
        val fish: AbsAnimal = Fish()
        fish.live()
        println("====================================")
        println("Lion的生活:")
        val lion: AbsAnimal = Lion()
        lion.live()
        println("============模板模式示例============")
    }

    companion object {

        /**
         * 运行场景
         */
        fun run() {
            TemplatePatternSceneSimulator().run()
        }
    }
}

测试入口类Main代码如下:

package com.xy.designpatterns.template

import org.junit.jupiter.api.Test

/**
 * 模板模式测试类
 */
class Main {

    /**
     * 演示模板模式
     */
    @Test
    fun main() {
        TemplatePatternSceneSimulator.run()
    }
}

我们执行Main#main()方法后,控制台输出如下日志:

============模板模式示例============
Bird的生活:
小鸟吃昆虫
小鸟在天空飞翔
小鸟在树上的鸟窝里面睡觉
====================================
Fish的生活:
小鱼吃水草
小鱼在水里游动
小鱼钻进沙里睡觉
====================================
Lion的生活:
狮子吃羚羊
狮子在草原奔跑
狮子趴在树下睡觉
============模板模式示例============

可以看到,BirdFishLion都遵循了AbsAnimal制定的模板框架(live),并各自实现了对框架内每个抽象环节(eat、move、sleep)的自定义。

总结

简单来说,模板模式就是抽取相似性事物的共性,并将这些共性进行封装,然后对差异性进行隔离,即把差异性通过抽象的方式暴露出来,交还给具体的事物去实现。
在平时的开发工作中,如果我们遇到的问题具有多种不同的场景,不同场景之间既有差异又有共性,那么就可以考虑使用模板模式来进行设计。

Q.E.D.


学而时习之,不亦说乎? 有朋自远方来,不亦乐乎?