Go -- 模板方法模式 (Template Method)

180 阅读11分钟

一、定义

1.1 核心概念

模板方法模式 (Template Method Pattern) 是一种行为设计模式,它在一个方法中定义了一个算法的骨架,并将某些步骤推迟到子类中实现。模板方法允许子类在不改变算法结构的情况下,重新定义算法中的某些步骤。

1.2 官方定义

定义:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。

1.3 Go语言特性

在Go语言中,由于没有类和继承的概念,模板方法模式通常通过以下方式实现:

  • 接口:定义算法步骤
  • 结构体:作为模板和具体实现
  • 组合:代替继承
  • 函数类型:作为回调

二、模式结构

2.1 核心组件

组件1:模板接口(抽象接口)

TemplateInterface 抽象接口 - 定义算法的骨架方法

Step1 Step2 原语操作(Primitive Operations)- 必须由子类实现

  • Step1-- 步骤1:抽象方法,必须实现
  • Step2-- 步骤2:抽象方法,必须实现

HookStep HookAfterStep 钩子方法(Hook Methods)- 可选实现,控制流程

  • HookStep -- 钩子方法:决定是否执行某个步骤
  • HookAfterStep-- 钩子方法:步骤后处理(可空实现)
type TemplateInterface interface {
    Step1()
    Step2()
    
    HookStep() bool 
    HookAfterStep() 

    // 具体方法(Concrete Methods)- 可在接口中提供默认实现
    // 在Go中,通常通过嵌入结构体或提供辅助函数来实现
}

我的疑问:

1、HookAfterStep-- 钩子方法:步骤后处理(可空实现)的 可空实现 是什么意思?

钩子方法有两种类型:一种是 返回布尔值 ,用于控制算法流程(如是否执行某一步);另一种是 空方法 ,子类可以选择性地覆盖,以在算法的特定点插入自定义行为。HookAfterStep() 就是第二种钩子方法,它在模板方法的某个步骤之后被调用,默认是空实现,子类可以根据需要覆盖它,执行一些额外的操作

2、为什么这些方法都没有方法体?

Go 语言特性之一:Go 接口‌仅声明方法签名‌,‌不能包含方法体


组件2:模板结构体

情况一: 无状态模板(空结构体模板

空结构体,零内存占用

type Template struct{}

情况二: 有状态模板(带配置和上下文)

有状态模板包含的三类信息:

  • 配置信息 (Configuration) - 影响行为
  • 执行上下文 (Context) - 记录当前执行
  • 基础设施 (Infrastructure) - 辅助功能
type Template struct {
    // 配置信息 (Configuration) - 影响行为
    config *TemplateConfig    // 如何处理任务
    
    // 执行上下文 (Context) - 记录当前执行
    context *TemplateContext  // 执行过程中的状态
    
    // 基础设施 (Infrastructure) - 辅助功能
    logger  Logger    // 记录日志
    metrics Metrics   // 收集指标
    cache   Cache     // 缓存数据
    db      Database  // 数据库连接
}

配置结构体 - 定义模板的行为方式
type TemplateConfig struct {
    // 1. 开关配置
    EnableLogging   bool    // 是否启用日志
    EnableMetrics   bool    // 是否收集指标
    EnableCaching   bool    // 是否启用缓存
    StrictMode      bool    // 是否严格模式(不允许跳过步骤)
    
    // 2. 限制配置
    MaxRetries      int             // 最大重试次数
    Timeout         time.Duration   // 超时时间
    MaxConcurrency  int             // 最大并发数
    MaxItems        int             // 最大处理项数
    
    // 3. 算法配置
    Strategy        string          // 处理策略("fast", "safe", "balanced")
    Priority        int             // 优先级
    QualityLevel    string          // 质量级别("low", "medium", "high")
    
    // 4. 资源配置
    MemoryLimit     int64           // 内存限制(字节)
    TempDir         string          // 临时目录
    OutputFormat    string          // 输出格式
}

上下文结构体 - 记录单个执行过程的状态
type TemplateContext struct {
    // 1. 时间信息
    StartTime      time.Time     // 开始时间
    EndTime        time.Time     // 结束时间(完成后设置)
    LastUpdateTime time.Time     // 最后更新时间
    
    // 2. 进度信息
    StepCount      int           // 当前步骤数
    TotalSteps     int           // 总步骤数
    CurrentStep    string        // 当前步骤名
    Progress       float64       // 进度百分比(0.0-1.0)
    
    // 3. 资源信息
    MemoryUsed     int64         // 已使用内存
    CPUUsage       float64       // CPU使用率
    DiskUsage      int64         // 磁盘使用量
    
    // 4. 结果信息
    TotalCost      float64       // 总花费(金钱、时间等)
    StepResults    []StepResult  // 每一步的结果
    FinalResult    interface{}   // 最终结果
    
    // 5. 状态信息
    Status         string        // 状态("pending", "running", "completed", "failed")
    Error          error         // 错误信息(如果有)
    RetryCount     int           // 重试次数
    Cancelled      bool          // 是否被取消
    
    // 6. 业务数据
    InputData      interface{}   // 输入数据
    OutputData     interface{}   // 输出数据
    Metadata       map[string]interface{} // 元数据
}

基础设施接口定义
type Logger interface {
    Info(msg string, args ...interface{})
    Error(msg string, args ...interface{})
    Debug(msg string, args ...interface{})
    WithFields(fields map[string]interface{}) Logger
}

type Metrics interface {
    IncrementCounter(name string, tags map[string]string)
    RecordHistogram(name string, value float64, tags map[string]string)
    SetGauge(name string, value float64, tags map[string]string)
}

type Cache interface {
    Get(key string) (interface{}, bool)
    Set(key string, value interface{}, ttl time.Duration)
    Delete(key string)
}

type Database interface {
    // 查询方法
    Query(query string, args ...interface{}) (*sql.Rows, error)
    QueryRow(query string, args ...interface{}) *sql.Row
    Exec(query string, args ...interface{}) (sql.Result, error)
    
    // 事务方法
    Begin() (*sql.Tx, error)
    BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error)
    
    // 连接管理
    Ping() error
    Close() error
    
    // 统计信息
    Stats() sql.DBStats
}


组件3:模板方法

模板方法 (Template Method) : 这是一个定义在模板中的方法,它提供了一个算法的骨架,按照一定的顺序调用其他方法(包括抽象方法和具体方法)。模板方法通常是不可被重写的,它们代表了算法中不可变的步骤。(在Go中,我们可以通过不暴露该方法为接口的一部分来防止重写,但这里我们使用结构体嵌入,子类可以重写,但通常我们不希望子类重写模板方法,因为这会改变算法结构)。
抽象方法 (Abstract Methods) : 这些方法在模板中声明(通常是通过接口),但是由子类实现。它们代表了算法中可变的步骤
具体方法 (Concrete Methods) : 这些方法在模板中已经有默认实现,子类可以选择使用或重写。
钩子方法 (Hook Methods) : 钩子方法是在模板中声明并提供了默认实现的方法,子类可以选择是否重写。它们通常用于在算法的特定点提供扩展能力。

// 模板方法 (Template Method) - 定义在模板中
func (t *Template) Execute(impl TemplateInterface) error {
    // 算法的骨架 - 固定顺序
    t.beforeExecute()     // 具体方法
    impl.Step1()          // 抽象方法
	
    if impl.HookStep() {  // 钩子方法控制流程
        impl.Step2()      // 抽象方法
    }
	
    t.afterExecute()         // 具体方法
    impl.HookAfterStep()     // 钩子方法
	
    return nil
}

// 具体方法 (Concrete Methods) - 在模板中提供默认实现
func (t *Template) beforeExecute() {
	fmt.Println("默认前置处理")
}

func (t *Template) afterExecute() {
	fmt.Println("默认后置处理")
}

概念Go语言实现方式作用是否必须能否被覆盖
模板方法模板结构体的方法定义算法的骨架和固定执行顺序不应被覆盖
抽象方法接口中的方法声明必须由子类实现的步骤接口要求必须实现必须实现
具体方法模板结构体的方法提供默认实现,处理通用逻辑否(有默认)可选择性覆盖
钩子方法接口中的方法提供扩展点,控制流程分支是(但可为空)可选择性覆盖

组件4:具体实现类

具体实现类是模板方法模式中实现接口具体逻辑的类,它:

  • 实现接口:必须实现所有抽象方法
  • 继承/嵌入模板:获得模板的默认实现
  • 提供具体业务逻辑:实现特定的功能
// 具体实现类
type ConcreteImpl struct {
	Template // 嵌入模板,获得具体方法的默认实现
}

// 实现抽象方法 (必须)
func (c *ConcreteImpl) Step1() {
	fmt.Println"实现 Step1")
}

func (c *ConcreteImpl) Step2() {
	fmt.Println"实现 Step2")
}

// 实现钩子方法 (可选,但有默认行为)
func (c *ConcreteImpl) HookStep() bool {
	return true // 默认返回 true
}

func (c *ConcreteImpl)Hook AfterStep() {
	// 空实现 - 什么也不做
}

// 覆盖具体方法 (可选)
func (c *ConcreteImpl) beforeExecute() {
	// 调用父类的默认实现
	c.Template.beforeExecute()
	fmt.Println("扩展前置处理")
}

func (c *ConcreteImpl) afterExecute() {
	// 调用父类的默认实现
	c.Template.afterExecute()
	fmt.Println("扩展后置处理")
}

实例化

func main() {
	// 创建模板实例
	template := &Template{}
	
	// 测试具体实现
	impl := ConcreteImpl{}
	template.Execute(impl)
}
扩展前置处理
实现 Step1
实现 Step2
扩展后置处理

2.2 时序图

sequenceDiagram
    participant Main as main函数
    participant Template as Template实例
    participant Interface as TemplateInterface接口
    participant Impl as ConcreteImpl实例
    participant TemplateImpl as Template方法
    
    Main->>Template: Execute(impl)
    
    %% 步骤1:调用beforeExecute
    Template->>TemplateImpl: beforeExecute()
    Note over TemplateImpl: 具体方法<br/>模板提供默认实现
    TemplateImpl-->>Template: 返回
    
    %% 步骤2:调用Step1(抽象方法)
    Template->>Interface: Step1()
    Interface->>Impl: 调用具体实现
    Note over Impl: 必须实现抽象方法
    Impl-->>Interface: 完成
    Interface-->>Template: 返回
    
    %% 步骤3:调用HookStep(钩子方法)
    Template->>Interface: HookStep()
    Interface->>Impl: 调用具体实现
    Note over Impl: 钩子方法:控制流程
    Impl-->>Interface: 返回true/false
    Interface-->>Template: 钩子结果
    
    alt HookStep返回true
        %% 步骤4:调用Step2(抽象方法)
        Template->>Interface: Step2()
        Interface->>Impl: 调用具体实现
        Impl-->>Interface: 完成
        Interface-->>Template: 返回
    else HookStep返回false
        Note over Template: 跳过Step2
    end
    
    %% 步骤5:调用afterExecute
    Template->>TemplateImpl: afterExecute()
    Note over TemplateImpl: 具体方法<br/>模板提供默认实现
    TemplateImpl-->>Template: 返回
    
    %% 步骤6:调用HookAfterStep(钩子方法)
    Template->>Interface: HookAfterStep()
    Interface->>Impl: 调用具体实现
    Note over Impl: 钩子方法:后处理
    Impl-->>Interface: 完成
    Interface-->>Template: 返回
    
    Template-->>Main: 返回nil

附件

角色养成

尝试

想写个角色养成

  • 树脂 --> 收集材料 --> 等级升级
  • 树脂 --> 刷材料 --> 武器升级
  • 树脂 --> 刷材料 --> 天赋升级
  • 树脂 --> 刷圣遗物 --> 装备强化
    = 树脂 --> 获取材料 --> 升级
package main

import "fmt"

// ---------------------------------- 模板接口 ----------------------------------

// CharacterDevelopment 角色养成
type CharacterDevelopment interface {
    Resin()         // 树脂
    Material()      // 刷材料
    Upgrade()       // 升级
    Equip()         // 装备
    IfRetain() bool // 是否保留圣遗物
}

// ---------------------------------- 模板结构体 ----------------------------------

// SacredRelic 圣遗物
type SacredRelic struct {
}

// ---------------------------------- 模板方法 ----------------------------------

func (s *SacredRelic) Execute(c CharacterDevelopment) error {
    s.beforeExecute()
    c.Resin()
    c.Material()
    c.Upgrade()
    if c.IfRetain() == true {
       c.Equip()
    }
    s.afterExecute()
    return nil
}

func (s *SacredRelic) beforeExecute() {
    fmt.Println("圣遗物养成开始")
}

func (s *SacredRelic) afterExecute() {
    fmt.Println("圣遗物养成结束")
}

// ---------------------------------- 具体实现类 ----------------------------------

type Venti struct {
}

func (v Venti) Resin() {
    fmt.Println("等待树脂恢复,目前有180树脂")
}

func (v Venti) Material() {
    fmt.Println("刷圣遗物:翠绿之影4件套(风套)")
}

func (v Venti) Upgrade() {
    fmt.Println("有个精通头,副词条有双爆,升级它")
}

func (v Venti) IfRetain() bool {
    return true
}

func (v Venti) Equip() {
    fmt.Println("副词条全中有双爆,保留它,现在装备上")
}

// ---------------------------------- 实例化 ----------------------------------

func main() {
    sacredRelic := &SacredRelic{}
    venti := Venti{}
    err := sacredRelic.Execute(venti)
    if err != nil {
       return
    }
}
圣遗物养成开始
等待树脂恢复,目前有180树脂
刷圣遗物:翠绿之影4件套(风套)
有个精通头,副词条有双爆,升级它
副词条全中有双爆,保留它,现在装备上
圣遗物养成结束

时序图


sequenceDiagram
    participant Client as 客户端(main)
    participant Template as SacredRelic<br>(模板类)
    participant Concrete as Venti<br>(具体实现)
    
    Client->>Template: sacredRelic.Execute(venti)
    
    Note over Template: beforeExecute()
    Template->>Template: beforeExecute()
    
    Note over Template: 执行模板方法流程
    
    Template->>Concrete: Resin()
    Concrete-->>Template: 
    Template->>Concrete: Material()
    Concrete-->>Template: 
    Template->>Concrete: Upgrade()
    Concrete-->>Template: 
    Template->>Concrete: IfRetain()
    Concrete-->>Template: true
    
    alt IfRetain() == true
        Template->>Concrete: Equip()
        Concrete-->>Template: 
    end
    
    Note over Template: afterExecute()
    Template->>Template: afterExecute()
    
    Template-->>Client: nil (成功返回)

优化

命名更具体:方法名明确表达功能
模板类有状态:可以记录开始/结束时间
使用 defer :确保 onFinish() 始终执行
新增回调方法:OnSuccess() 和 OnFailure()
更丰富的日志:包含时间和耗时统计

package main

import (
	"fmt"
	"time"
)

// ---------------------------------- 模板接口 ----------------------------------

// CharacterCultivation 角色养成(更具体的命名)
type CharacterCultivation interface {
	CheckResources()          // 检查资源(原Resin)
	CollectMaterials()        // 收集材料(原Material)
	EnhanceArtifact()         // 强化圣遗物(原Upgrade)
	ShouldKeepArtifact() bool // 是否保留圣遗物(原IfRetain)
	EquipArtifact()           // 装备圣遗物(原Equip)
	OnSuccess()               // 成功回调(新增)
	OnFailure()               // 失败回调(新增)
}

// ---------------------------------- 模板结构体 ----------------------------------

// ArtifactUpgradeTemplate 圣遗物升级模板(更通用的模板)
type ArtifactUpgradeTemplate struct {
	Name      string
	StartTime time.Time
	EndTime   time.Time
}

// ---------------------------------- 模板方法 ----------------------------------

func (a *ArtifactUpgradeTemplate) Execute(c CharacterCultivation) error {
	a.onStart()
	defer a.onFinish() // 使用 defer 确保始终执行

	// 检查资源
	c.CheckResources()
	// 收集材料
	c.CollectMaterials()
	// 强化圣遗物
	c.EnhanceArtifact()
	// 根据钩子方法决定是否装备
	if c.ShouldKeepArtifact() {
		c.EquipArtifact()
		c.OnSuccess()
	} else {
		c.OnFailure()
	}

	return nil
}

// 私有方法,模板内部使用
func (a *ArtifactUpgradeTemplate) onStart() {
	a.StartTime = time.Now()
	fmt.Printf("【%s】圣遗物强化开始于: %s\n", a.Name, a.StartTime.Format("15:04:05"))
}

func (a *ArtifactUpgradeTemplate) onFinish() {
	a.EndTime = time.Now()
	duration := a.EndTime.Sub(a.StartTime)
	fmt.Printf("【%s】圣遗物强化结束于: %s,耗时: %.2f秒\n", a.Name, a.EndTime.Format("15:04:05"), duration.Seconds())
}

// ---------------------------------- 具体实现类 ----------------------------------

type VentiCultivation struct {
	ArtifactQuality string // 圣遗物养成情况
	ResinCount      int    // 树脂数量
}

func (v VentiCultivation) CheckResources() {
	if v.ResinCount < 20 {
		fmt.Printf("树脂不足!当前: %d/40,等待恢复...\n", v.ResinCount)
	} else {
		fmt.Printf("树脂充足!当前: %d/40\n", v.ResinCount)
	}
}

func (v VentiCultivation) CollectMaterials() {
	fmt.Println("前往「铭记之谷」副本;击败敌人,获得:「翠绿之影」圣遗物;消耗40树脂领取奖励")
}

func (v VentiCultivation) EnhanceArtifact() {
	fmt.Println("选择圣遗物进行强化...")
	// 模拟强化过程
	// 实际上是更复杂的判断逻辑
	var n int
	switch v.ArtifactQuality {
	case "Gold":
		n = 5
	case "Purple":
		n = 4
	case "Blue":
		n = 3
	case "Green":
		n = 2
	case "White":
		n = 1
	default:
		fmt.Println("没有该品质的圣遗物")
	}
	for i := 1; i <= n; i++ {
		fmt.Printf("  强化+%d: 随机提升副词条\n", i*4)
		time.Sleep(200 * time.Millisecond)
	}
}

func (v VentiCultivation) ShouldKeepArtifact() bool {
	// 模拟随机决定
	// 实际上是更复杂的判断逻辑
	if v.ArtifactQuality != "Gold" {
		return false
	}
	return true
}

func (v VentiCultivation) EquipArtifact() {
	fmt.Println("🎉 强化成功!为「温迪」装备新圣遗物!")
}

func (v VentiCultivation) OnSuccess() {
	fmt.Println("✅ 养成成功!角色战斗力提升")
}

func (v VentiCultivation) OnFailure() {
	fmt.Println("❌ 养成失败,圣遗物作为狗粮使用")
}

// ---------------------------------- 实例化 ----------------------------------

func main() {
	// 温迪的养成
	template := &ArtifactUpgradeTemplate{Name: "温迪"}
	venti := VentiCultivation{
		ArtifactQuality: "Gold",
		ResinCount:      180,
	}
	template.Execute(venti)
}

【温迪】圣遗物强化开始于: 11:20:27
树脂充足!当前: 180/40
前往「铭记之谷」副本;击败敌人,获得:「翠绿之影」圣遗物;消耗40树脂领取奖励
选择圣遗物进行强化...
  强化+4: 随机提升副词条
  强化+8: 随机提升副词条
  强化+12: 随机提升副词条
  强化+16: 随机提升副词条
  强化+20: 随机提升副词条
🎉 强化成功!为「温迪」装备新圣遗物!
✅ 养成成功!角色战斗力提升
【温迪】圣遗物强化结束于: 11:20:28,耗时: 1.01秒

ps:尝试写写的这个思路感觉不太适合这个模板设计模式,但可以简单写个流程看看样子,差不多就这样吧


时序图


sequenceDiagram
    participant Client as 客户端(main)
    participant Template as ArtifactUpgradeTemplate<br>(模板类)
    participant Interface as CharacterCultivation<br>(接口)
    participant Venti as VentiCultivation<br>(具体实现)
    
    Client->>Template: Execute(venti)
    
    Note over Template: 1. 开始处理
    Template->>Template: onStart()
    
    Note over Template: 2. 固定流程执行
    
    Template->>Venti: CheckResources()
    Venti-->>Template: 
    Template->>Venti: CollectMaterials()
    Venti-->>Template: 
    Template->>Venti: EnhanceArtifact()
    Venti-->>Template: 
    
    Note over Template: 3. 钩子方法判断
    Template->>Venti: ShouldKeepArtifact()
    Venti-->>Template: true/false
    
    alt 保留圣遗物
        Template->>Venti: EquipArtifact()
        Venti-->>Template: 
        Template->>Venti: OnSuccess()
        Venti-->>Template: 
    else 不保留
        Template->>Venti: OnFailure()
        Venti-->>Template: 
    end
    
    Note over Template: 4. 结束处理(使用defer)
    Template->>Template: onFinish()
    
    Template-->>Client: 返回结果