模板方法模式属于行为型设计模式,它的核心逻辑是把一个算法的整体流程固定下来,只把流程中某些可变的步骤交给不同的实现类去完成。Golang中没有传统面向对象语言的继承和抽象类概念,但是可以通过接口和结构体嵌入的方式来实现模板方法模式的效果,既能保证算法流程的统一,又能让不同场景的具体实现灵活变化。

模板方法模式的核心结构
在Golang中实现模板方法模式通常需要三个部分:
- 抽象接口:定义算法流程中需要子类实现的具体步骤方法
- 模板结构体:实现算法的整体流程,调用抽象接口中定义的方法,同时嵌入抽象接口类型方便子类重写
- 具体实现结构体:实现抽象接口中定义的可变步骤方法,完成不同场景下的具体逻辑
具体实现步骤
第一步:定义抽象接口
首先我们需要定义一个接口,里面包含算法流程中需要子类自定义实现的步骤方法,以及可选的钩子方法用于扩展流程。
// 抽象接口,定义算法中需要子类实现的步骤
type DataProcessor interface {
// 读取数据,需要子类实现具体逻辑
ReadData() error
// 处理数据,需要子类实现具体逻辑
ProcessData() error
// 写入数据,需要子类实现具体逻辑
WriteData() error
// 钩子方法,默认返回true,子类可以重写改变流程
NeedValidate() bool
}
第二步:实现模板结构体
模板结构体负责定义算法的整体流程,它会调用接口中定义的方法,同时嵌入接口类型,这样子类只需要实现接口方法就可以被模板调用。
// 模板结构体,定义算法整体流程
type BaseProcessor struct {
// 嵌入接口类型,子类会替换这个字段为自己的实现
DataProcessor
}
// 模板方法,定义算法整体流程
func (b *BaseProcessor) Process() error {
// 第一步:读取数据
if err := b.ReadData(); err != nil {
return err
}
// 钩子判断是否需要验证
if b.NeedValidate() {
// 验证数据,公共逻辑可以写在模板里
if err := b.validateData(); err != nil {
return err
}
}
// 第二步:处理数据
if err := b.ProcessData(); err != nil {
return err
}
// 第三步:写入数据
if err := b.WriteData(); err != nil {
return err
}
return nil
}
// 公共验证逻辑,所有处理器都会用到
func (b *BaseProcessor) validateData() error {
// 这里可以写通用的数据验证逻辑
return nil
}
// 默认钩子方法实现,返回true表示需要验证
func (b *BaseProcessor) NeedValidate() bool {
return true
}
第三步:实现具体子类
具体子类只需要实现DataProcessor接口中定义的方法即可,不需要关心整体流程,流程由模板结构体控制。
// 文件数据处理的具体实现
type FileDataProcessor struct {
// 嵌入BaseProcessor,继承模板方法
BaseProcessor
// 具体实现需要的字段
filePath string
}
// 初始化文件处理器
func NewFileDataProcessor(path string) *FileDataProcessor {
p := &FileDataProcessor{filePath: path}
// 把当前实例赋值给嵌入的DataProcessor字段,这样模板调用方法时会调用当前实例的实现
p.DataProcessor = p
return p
}
// 实现读取数据方法
func (f *FileDataProcessor) ReadData() error {
// 具体读取文件的逻辑
return nil
}
// 实现处理数据方法
func (f *FileDataProcessor) ProcessData() error {
// 具体处理文件数据的逻辑
return nil
}
// 实现写入数据方法
func (f *FileDataProcessor) WriteData() error {
// 具体写入文件的逻辑
return nil
}
// 数据库数据处理的具体实现
type DBDataProcessor struct {
BaseProcessor
dbAddr string
}
// 初始化数据库处理器
func NewDBDataProcessor(addr string) *DBDataProcessor {
p := &DBDataProcessor{dbAddr: addr}
p.DataProcessor = p
return p
}
// 实现读取数据方法
func (d *DBDataProcessor) ReadData() error {
// 具体从数据库读取数据的逻辑
return nil
}
// 实现处理数据方法
func (d *DBDataProcessor) ProcessData() error {
// 具体处理数据库数据的逻辑
return nil
}
// 实现写入数据方法
func (d *DBDataProcessor) WriteData() error {
// 具体写入数据库的逻辑
return nil
}
// 重写钩子方法,数据库数据不需要验证
func (d *DBDataProcessor) NeedValidate() bool {
return false
}
使用示例
完成上面的定义之后,我们就可以很方便地使用不同的处理器来执行统一的算法流程了。
func main() {
// 使用文件处理器
fileProcessor := NewFileDataProcessor("/tmp/data.txt")
if err := fileProcessor.Process(); err != nil {
// 处理错误
}
// 使用数据库处理器
dbProcessor := NewDBDataProcessor("ipipp.com:3306")
if err := dbProcessor.Process(); err != nil {
// 处理错误
}
}
注意事项
在Golang中实现模板方法模式需要注意几个点:
- 一定要把具体子类的实例赋值给模板结构体中嵌入的接口字段,否则模板调用方法时会调用到默认实现或者出现空指针错误
- 钩子方法不是必须的,如果不需要扩展流程可以不定义,默认实现放在模板结构体中即可
- 如果算法流程中有多个可变步骤,接口中定义的方法可以灵活调整,不需要把所有步骤都暴露给子类,公共逻辑尽量放在模板结构体中减少重复
这种实现方式既保留了模板方法模式的优势,又符合Golang的语言特性,在需要统一算法流程但不同场景实现细节不同的场景下非常实用。