设计模式之模板方式模式

29 阅读5分钟

前言

对于模板方法模式,其实仔细想想,在现实生活中就有很多场景满足这一模式。"模板方法"中"模板"一词,顾名思义就是有一个固定的过程(或者称结构,流程,步骤等等)。联想下老板让你汇报工作、公司让你做季度总结时的ppt,这时就会在网上或者在同事那儿找一份ppt模版,然后在模板的基础上编辑属于自己的内容。再如,老板PUA员工时,说得天花乱坠,从否定员工到公司的大恩大德,再到感恩戴德,为理想奋斗等,面对不同的员工可能在细节上有所差别,但往往都是一套固定的话术模式。这就是模板方式模式,被无良老板们玩得那叫一个6。这些场景可以总结为:当我们要完成一个一致的过程或一系列步骤,但只在个别步骤更详细的层次上实现可能不同时,就应该使用模板方法模式来处理。

需求假设

可以假定这样一个场景来具体说明模板方法模式的应用——即古代铸剑。古时候铸剑整体上都有一套固定的铸造流程,但不同的人例如师傅和徒弟,可能打造出不同品质的剑。这是因为在细节步骤上,有经验的老师傅和初出茅庐的徒弟各自的掌控和处理有所差异。古时候铸剑一般分为五个工序:

  • 制做泥范

    用泥塑造剑范,然后放入窑中经火烘干。

  • 调剂材料

    根据合理的配比规律,对铜、锡、铅等原料进行调配。

  • 熔炼原料

    将铜、锡、铅等原料进行熔炼,进一步去除原料中含有的杂质。

  • 浇灌成形

    将熔炼成熟的青铜液体浇灌入剑范成形。

  • 修治加工

    浇铸出来的铜剑表面精糙,需卸去铸范后,进行修治加工。

经过这五道工序,老师傅手艺高超可能打造出"绝世好剑",而小徒弟羽翼未丰只能打造出"村好剑"。

模式定义

模板方法模式:定义一个操作中算法的骨架,而将一些步骤延迟到子类

模板方法模式可以使得子类不改变一个算法的结构,即可重定义该算法的某些特定步骤。

  • 这里的"算法",理解为一系列固定的步骤、一个统一的过程;

  • "将一些步骤延迟到子类",就是要在某些特定的细节层次上实现不同,而整体的过程步骤保持不变

templateMethod.png

模式构成

模板方法模式由一个统一的模板方法TemplateMethod() 和各个步骤的细节操作PrimitiveOperation()构成:

  • TemplateMethod()内依次执行各个步骤的操作PrimitiveOperation1()、PrimitiveOperation2();

    例如铸剑的模板方法ForgeSword(),需要依次执行制做泥范MakeClayModel()、调剂材料DispenseMaterials()、熔炼原料SmeltingMaterials()、浇灌成形WaterShaping()、修治加工RepairProcessing()五个一致的步骤。

  • PrimitiveOperation()具体操作可以延迟到子类中去,由子类根据需要去继承实现特定于子类的不同操作细节。

代码示例

golang

/*
 * File: template.go
 * Created Date: 2023-03-13 02:44:39
 * Author: ysj
 * Description:  golang 模板方法
 */
package main

import "fmt"

type ForgeSwordTemplate interface {
    ForgeSword()        // 铸剑模板方法
    makeClayModel()     // 制做泥范, 每道工序20分
    dispenseMaterials() // 调剂材料, 每道工序20分
    smeltingMaterials() // 熔炼原料, 每道工序20分
    waterShaping()      // 浇灌成形, 每道工序20分
    repairProcessing()  // 修治加工, 每道工序20分
}

type ForgeSwordBase struct {
    Score int
}

func (f *ForgeSwordBase) ForgeSword() {
    f.makeClayModel()
    f.dispenseMaterials()
    f.smeltingMaterials()
    f.waterShaping()
    f.repairProcessing()
    f.result()
}

func (f *ForgeSwordBase) makeClayModel() {
    fmt.Println("制做泥范完美 +20分")
    f.Score += 20
}

func (f *ForgeSwordBase) dispenseMaterials() {
    fmt.Println("调剂材料完美 +20分")
    f.Score += 20
}

func (f *ForgeSwordBase) smeltingMaterials() {
    fmt.Println("熔炼原料完美 +20分")
    f.Score += 20
}

func (f *ForgeSwordBase) waterShaping() {
    fmt.Println("浇灌成形完美 +20分")
    f.Score += 20
}

func (f *ForgeSwordBase) repairProcessing() {
    fmt.Println("修治加工完美 +20分")
    f.Score += 20
}

func (f *ForgeSwordBase) result() {
    fmt.Println("总分:", f.Score)
    if f.Score == 100 {
        fmt.Println("获得绝世好剑!!!")
    } else if f.Score > 80 {
        fmt.Println("获得一把好剑!!")
    } else {
        fmt.Println("获得一把村好剑!")
    }
}
/*
 * File: oldMaster.go
 * Created Date: 2023-03-13 02:57:46
 * Author: ysj
 * Description:  模板方式模式——老师傅铸剑
 */

package main

type OldMasterForgeSword struct {
    ForgeSwordBase
}

func NewOldMasterForgeSword() ForgeSwordTemplate {
    return &OldMasterForgeSword{}
}
/*
 * File: littleApprentice.go
 * Created Date: 2023-03-13 03:00:07
 * Author: ysj
 * Description:  模板方式模式——小徒弟铸剑
 */

package main

import "fmt"

type LittleApprentice struct {
    ForgeSwordBase
}

// 初始化
func NewLittleApprentice() ForgeSwordTemplate {
    return &LittleApprentice{}
}

func (la *LittleApprentice) ForgeSword() {
    la.makeClayModel()
    la.dispenseMaterials()
    la.smeltingMaterials()
    la.waterShaping()
    la.repairProcessing()

    la.result()
}

func (la *LittleApprentice) dispenseMaterials() {
    fmt.Println("调剂材料比例不均 +10分")
    la.Score += 10
}

func (la *LittleApprentice) smeltingMaterials() {
    fmt.Println("熔炼原料火候不够 +10分")
    la.Score += 10
}
/*
 * File: main.go
 * Created Date: 2023-03-07 06:59:11
 * Author: ysj
 * Description: 模板方式模式——客户端调用
 */

package main

import "fmt"

func main() {
    fmt.Println("=============老师傅铸剑============")
    oldMaster := NewOldMasterForgeSword()
    oldMaster.ForgeSword()

    fmt.Println("=============小徒弟铸剑============")
    littleApprentice := NewLittleApprentice()
    littleApprentice.ForgeSword()
}
$ go run .

=============老师傅铸剑============
制做泥范完美 +20分
调剂材料完美 +20分
熔炼原料完美 +20分
浇灌成形完美 +20分
修治加工完美 +20分
总分: 100
获得绝世好剑!!!
=============小徒弟铸剑============
制做泥范完美 +20分
调剂材料比例不均 +10分
熔炼原料火候不够 +10分
浇灌成形完美 +20分
修治加工完美 +20分
总分: 80
获得一把村好剑!

python

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
###
# File: template.py
# Created Date: 2023-03-13 03:21:58
# Author: ysj
# Description:  python 模板方法
###

from abc import ABCMeta, abstractmethod


class ForgeSwordTemplate(metaclass=ABCMeta):
    score = 0

    def forge_sword(self):
        '''铸剑模板方法'''
        self.make_clay_model()
        self.dispense_materials()
        self.smelting_materials()
        self.water_shaping()
        self.repair_processing()

        self.result()

    @abstractmethod
    def make_clay_model(self):
        '''制做泥范, 每道工序20分'''
        print("制做泥范完美 +20分")
        self.score += 20

    @abstractmethod
    def dispense_materials(self):
        '''调剂材料, 每道工序20分'''
        print("调剂材料完美 +20分")
        self.score += 20

    @abstractmethod
    def smelting_materials(self):
        '''熔炼原料, 每道工序20分'''
        print("熔炼原料完美 +20分")
        self.score += 20

    @abstractmethod
    def water_shaping(self):
        '''浇灌成形, 每道工序20分'''
        print("浇灌成形完美 +20分")
        self.score += 20

    @abstractmethod
    def repair_processing(self):
        '''修治加工, 每道工序20分'''
        print("修治加工完美 +20分")
        self.score += 20

    def result(self):
        print("总分:", self.score)
        if self.score == 100:
            print("获得绝世好剑!!!")
        elif self.score > 80:
            print("获得一把好剑!!")
        else:
            print("获得一把村好剑!")
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
###
# File: old_master.py
# Created Date: 2023-03-13 03:22:45
# Author: ysj
# Description:  模板方式模式——老师傅铸剑
###

from template import ForgeSwordTemplate


class OldMasterForgeSword(ForgeSwordTemplate):
    def make_clay_model(self):
        '''制做泥范, 每道工序20分'''
        super().make_clay_model()

    def dispense_materials(self):
        '''调剂材料, 每道工序20分'''
        super().dispense_materials()

    def smelting_materials(self):
        '''熔炼原料, 每道工序20分'''
        super().smelting_materials()

    def water_shaping(self):
        '''浇灌成形, 每道工序20分'''
        super().water_shaping()

    def repair_processing(self):
        '''修治加工, 每道工序20分'''
        super().repair_processing()
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
###
# File: little_apprentice.py
# Created Date: 2023-03-13 03:23:08
# Author: ysj
# Description:  模板方式模式——小徒弟铸剑
###

from template import ForgeSwordTemplate


class LittleApprenticeForgeSword(ForgeSwordTemplate):
    def make_clay_model(self):
        '''制做泥范, 每道工序20分'''
        super().make_clay_model()

    def dispense_materials(self):
        print("调剂材料比例不均 +10分")
        self.score += 10

    def smelting_materials(self):
        print("熔炼原料火候不够 +10分")
        self.score += 10

    def water_shaping(self):
        '''浇灌成形, 每道工序20分'''
        super().water_shaping()

    def repair_processing(self):
        '''修治加工, 每道工序20分'''
        super().repair_processing()
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
###
# File: main.py
# Created Date: 2023-03-13 03:22:16
# Author: ysj
# Description:  模板方式模式——客户端调用
###

from old_master import OldMasterForgeSword
from little_apprentice import LittleApprenticeForgeSword

print("=============老师傅铸剑============")
old_master = OldMasterForgeSword()
old_master.forge_sword()

print("=============小徒弟铸剑============")
little_apprentice = LittleApprenticeForgeSword()
little_apprentice.forge_sword()
$ python main.py

=============老师傅铸剑============
制做泥范完美 +20分
调剂材料完美 +20分
熔炼原料完美 +20分
浇灌成形完美 +20分
修治加工完美 +20分
总分: 100
获得绝世好剑!!!
=============小徒弟铸剑============
制做泥范完美 +20分
调剂材料比例不均 +10分
熔炼原料火候不够 +10分
浇灌成形完美 +20分
修治加工完美 +20分
总分: 80
获得一把村好剑!

适用场景

当遇到由一系列的步骤构成的过程需要执行时,这个过程从高层次上看是相同的,但有些步骤的具体实现可能不同,这时候就应该考虑使用模板方法模式。

模板方法模式起到的作用是,当不变的和可变的行为在方法的子类实现中混合在一起时,不变的行为就会在子类中重复出现,模板方法模式通过把不变的行为搬移到超类,从而去除了子类中的重复代码。

参考资料

程杰.大话设计模式[M].北京:清华大学出版社,2007.12