golang24种设计模式——行为型模式
行为型模式(Behavioral pattern)它主要关注对象之间的职责分配和通信方式。行为型模式的核心思想是通过定义对象之间的交互方式,来更好地实现系统的功能,同时降低对象之间的耦合度。 责任链
golang24种设计模式——行为型模式
发布时间:2025-01-13 (2025-01-13)

行为型模式(Behavioral pattern)它主要关注对象之间的职责分配和通信方式。行为型模式的核心思想是通过定义对象之间的交互方式,来更好地实现系统的功能,同时降低对象之间的耦合度。

责任链模式 Chain of Responsibility

是一种行为设计模式,它允许多个对象有机会处理请求,从而避免请求的发送者与接收者之间的耦合。责任链模式将这些对象连成一条链,并沿着这条链传递请求,直到有对象处理它为止。

核心思想:

  1. 解耦请求发送者和接收者:请求的发送者不需要知道具体由哪个对象处理请求,只需要将请求发送到链上即可。
  2. 动态组合处理逻辑:可以动态地组合处理者,灵活地调整处理顺序或增加新的处理者。
  3. 每个处理者只关心自己的职责:每个处理者只处理自己能处理的请求,不能处理的请求会传递给下一个处理者。

适用场景:

  • 当一个请求需要经过多个对象处理,但具体由哪个对象处理不确定时。
  • 当需要动态指定处理请求的对象时。
  • 当希望避免请求发送者与接收者之间的紧密耦合时。

实际应用场景:

  1. 中间件
  2. 审批流程

实现一下gin的中间件功能,中间件自己可以处理请求,可以拦截,可以放行

package main

import (
  "fmt"
  "net/http"
)

// Context 模拟 Gin 的上下文
type Context struct {
  Request  *http.Request
  Writer   http.ResponseWriter
  handlers []HandlerFunc // 责任链中的处理函数
  index    int           // 当前执行的处理函数索引
}

// HandlerFunc 定义处理函数类型
type HandlerFunc func(c *Context)

// Next 调用责任链中的下一个处理函数
func (c *Context) Next() {
  c.index++
  if c.index < len(c.handlers) {
    c.handlers[c.index](c)
  }
}

// Abort 终止责任链的执行
func (c *Context) Abort() {
  c.index = len(c.handlers) // 直接跳到末尾,不再执行后续处理函数
}

// Engine 模拟 Gin 的引擎
type Engine struct {
  handlers []HandlerFunc // 全局中间件和责任链
}

// Use 添加中间件到责任链
func (e *Engine) Use(middleware HandlerFunc) {
  e.handlers = append(e.handlers, middleware)
}

// ServeHTTP 实现 http.Handler 接口
func (e *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  c := &Context{
    Request:  r,
    Writer:   w,
    handlers: e.handlers,
    index:    -1,
  }
  c.Next() // 开始执行责任链
}

// LoggingMiddleware 示例中间件 1:日志记录
func LoggingMiddleware(c *Context) {
  fmt.Printf("Request: %s %s\n", c.Request.Method, c.Request.URL.Path)
  c.Next() // 传递给下一个中间件
}

// AuthMiddleware 示例中间件 2:认证检查
func AuthMiddleware(c *Context) {
  token := c.Request.Header.Get("Authorization")
  if token != "valid-token" {
    c.Writer.WriteHeader(http.StatusUnauthorized)
    c.Writer.Write([]byte("Unauthorized"))
    c.Abort() // 终止责任链
    return
  }
  c.Next() // 传递给下一个中间件
}

// HelloHandler 示例中间件 3:请求处理
func HelloHandler(c *Context) {
  c.Writer.WriteHeader(http.StatusOK)
  c.Writer.Write([]byte("Hello, World!"))
}

func main() {
  // 创建引擎
  engine := &Engine{}

  // 添加中间件到责任链
  engine.Use(LoggingMiddleware)
  engine.Use(AuthMiddleware)
  engine.Use(HelloHandler)

  // 启动 HTTP 服务器
  fmt.Println("Server is running on :8080")
  http.ListenAndServe(":8080", engine)
}

责任链模式是一种强大的行为型设计模式,特别适用于需要将请求的发送者和接收者解耦、支持多个处理者或动态地添加和移除处理者的场景。

通过引入责任链模式,可以将请求传递给多个处理者,直到有一个处理者能够处理该请求为止。这样不仅提高了代码的灵活性和可维护性,还增强了系统的扩展性。

命令模式 Command

它将请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,并且支持请求的排队、记录日志、撤销操作等功能。

核心思想:

  1. 将请求封装为对象:将每个请求(如方法调用)封装为一个独立的对象。
  2. 解耦请求的发送者和接收者:发送者不需要知道接收者的具体实现,只需要通过命令对象来执行请求。
  3. 支持扩展:可以轻松地添加新的命令,而不需要修改现有代码。

例子:假设我们有一个任务队列系统,用于处理用户提交的任务。每个任务都是一个命令对象,任务队列会依次执行这些命令。任务可以是简单的操作(如打印消息),也可以是复杂的操作(如发送邮件、处理数据等)

package main

import (
  "fmt"
  "time"
)

// Command 定义命令接口
type Command interface {
  Execute()
}

// PrintCommand 是具体命令,表示打印消息
type PrintCommand struct {
  message string
}

func (c *PrintCommand) Execute() {
  fmt.Println("Executing PrintCommand:", c.message)
}

// EmailCommand 是具体命令,表示发送邮件
type EmailCommand struct {
  to      string
  subject string
  body    string
}

func (c *EmailCommand) Execute() {
  fmt.Printf("Executing EmailCommand: Sending email to %s\n", c.to)
  fmt.Printf("Subject: %s\n", c.subject)
  fmt.Printf("Body: %s\n", c.body)
}

// TaskQueue 是任务队列,用于存储和执行命令
type TaskQueue struct {
  commands []Command
}

func (tq *TaskQueue) AddCommand(cmd Command) {
  tq.commands = append(tq.commands, cmd)
}

func (tq *TaskQueue) ProcessCommands() {
  for _, cmd := range tq.commands {
    cmd.Execute()
    time.Sleep(1 * time.Second) // 模拟任务执行的时间
  }
  tq.commands = nil // 清空任务队列
}

func main() {
  // 创建任务队列
  taskQueue := &TaskQueue{}

  // 添加任务到队列
  taskQueue.AddCommand(&PrintCommand{message: "Hello, World!"})
  taskQueue.AddCommand(&EmailCommand{
    to:      "user@example.com",
    subject: "Welcome",
    body:    "Thank you for signing up!",
  })
  taskQueue.AddCommand(&PrintCommand{message: "Task queue is running..."})

  // 处理任务队列
  fmt.Println("Processing task queue:")
  taskQueue.ProcessCommands()

  // 添加更多任务
  taskQueue.AddCommand(&PrintCommand{message: "Another task"})
  taskQueue.AddCommand(&EmailCommand{
    to:      "admin@example.com",
    subject: "Report",
    body:    "Here is the daily report.",
  })

  // 再次处理任务队列
  fmt.Println("\nProcessing task queue again:")
  taskQueue.ProcessCommands()
}

解释器模式 Interpreter

它定义了一种语言的语法表示,并提供了一个解释器来解释这种语法。解释器模式通常用于处理类似编程语言、查询语言、规则引擎等场景。

核心思想:

  1. 定义语法规则:将语言的语法规则表示为一个抽象语法树(AST)。
  2. 解释执行:通过解释器遍历抽象语法树,执行相应的操作。

抽象语法树

抽象语法树(Abstract Syntax Tree,AST) 是编译器和解释器中的一个核心概念。它是一种树形数据结构,用于表示源代码的语法结构。AST 之所以被称为“抽象语法树”,是因为它 抽象掉了源代码中的一些具体细节,只保留了程序的逻辑结构。

为什么叫“抽象语法树”?

  1. 抽象(Abstract):
    • AST 并不包含源代码中的所有细节,比如括号、分号、空格等。它只关注程序的逻辑结构。
    • 例如,表达式 2 * (3 + 4) 的 AST 会抽象掉括号,只保留运算符和操作数的层次关系。
  2. 语法(Syntax):
    • AST 表示的是源代码的语法结构,而不是语义(即程序的含义)。
    • 例如,AST 可以表示一个 if 语句的语法结构,但不会解释 if 语句的具体行为。
  3. 树(Tree):
    • AST 是一种树形数据结构,每个节点表示一个语法结构(如表达式、语句、函数等)。
    • 树的层次结构反映了源代码的嵌套关系。

比如

if x > 0 {
    print("Positive")
} else {
    print("Non-positive")
}

对应的AST就是

IfStatement
├── Condition: BinaryExpression
│   ├── Left: Identifier (x)
│   ├── Operator: >
│   └── Right: Number (0)
├── ThenBlock: BlockStatement
│   └── Expression: CallExpression
│       ├── Function: Identifier (print)
│       └── Arguments: StringLiteral ("Positive")
└── ElseBlock: BlockStatement
    └── Expression: CallExpression
        ├── Function: Identifier (print)
        └── Arguments: StringLiteral ("Non-positive")

解释器模式应用还是挺多的

  1. 数据库查询引擎:
    • 如 MySQL、PostgreSQL 等数据库的查询解析和执行。
  2. 规则引擎:
    • 如业务规则引擎,用于解析和执行规则。
  3. 模板引擎:
    • 如 Go 的 text/template,用于解析和执行模板。
  4. 数学表达式计算:
    • 如计算器应用程序,用于解析和计算数学表达式。

以go语言里面的模板字符串为例:

package main

import (
  "os"
  "text/template"
)

func main() {
  // 定义模板字符串
  const tmpl = `Hello, {{.Name}}! You are {{.Age}} years old.`

  // 解析模板
  t, err := template.New("example").Parse(tmpl)
  if err != nil {
    panic(err)
  }

  // 定义数据
  data := struct {
    Name string
    Age  int
  }{
    Name: "Alice",
    Age:  25,
  }

  // 执行模板并输出结果
  err = t.Execute(os.Stdout, data)
  if err != nil {
    panic(err)
  }
}

这个是解释器模式的应用,我们需要了解内部的原理,我们自己手搓一个这样的模板字符串解析器出来

package main

import (
  "fmt"
  "strings"
)

// Context 定义上下文,存储模板数据
type Context struct {
  Data map[string]interface{}
}

// Expression 定义抽象表达式接口
type Expression interface {
  Interpret(ctx *Context) string
}

// TextExpression 是文本表达式,表示普通文本
type TextExpression struct {
  text string
}

func (t *TextExpression) Interpret(ctx *Context) string {
  return t.text
}

// VariableExpression 是变量表达式,表示变量替换
type VariableExpression struct {
  key string
}

func (v *VariableExpression) Interpret(ctx *Context) string {
  value, ok := ctx.Data[v.key]
  if !ok {
    return ""
  }
  return fmt.Sprintf("%v", value)
}

// Template 是模板,包含多个表达式
type Template struct {
  expressions []Expression
}

func (t *Template) Interpret(ctx *Context) string {
  var result strings.Builder
  for _, expr := range t.expressions {
    result.WriteString(expr.Interpret(ctx))
  }
  return result.String()
}

// ParseTemplate 解析模板字符串并构建抽象语法树
func ParseTemplate(tmpl string) *Template {
  template := &Template{}
  start := 0
  for {
    // 查找变量表达式的起始位置
    startVar := strings.Index(tmpl[start:], "{{")
    if startVar == -1 {
      // 如果没有变量表达式,剩下的都是普通文本
      template.expressions = append(template.expressions, &TextExpression{text: tmpl[start:]})
      break
    }
    // 添加普通文本
    template.expressions = append(template.expressions, &TextExpression{text: tmpl[start : start+startVar]})
    // 查找变量表达式的结束位置
    endVar := strings.Index(tmpl[start+startVar:], "}}")
    if endVar == -1 {
      // 如果没有结束符,直接退出
      break
    }
    // 解析变量表达式
    key := strings.TrimSpace(tmpl[start+startVar+2 : start+startVar+endVar])
    template.expressions = append(template.expressions, &VariableExpression{key: key})
    // 更新起始位置
    start = start + startVar + endVar + 2
  }
  return template
}

func main() {
  // 定义模板字符串
  tmpl := "Hello, {{Name}}! You are {{Age}} years old."

  // 解析模板并构建抽象语法树
  template := ParseTemplate(tmpl)

  // 定义上下文数据
  ctx := &Context{
    Data: map[string]interface{}{
      "Name": "Alice",
      "Age":  25,
    },
  }

  // 解释执行模板并输出结果
  result := template.Interpret(ctx)
  fmt.Println(result) // 输出: Hello, Alice! You are 25 years old.
}

迭代器模式 Iterator

它提供了一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。迭代器模式的核心思想是将遍历逻辑从聚合对象中分离出来,使得聚合对象和遍历逻辑可以独立变化。

实际应用场景

  1. 集合类库:
    • 如 Go 的 slicemap 等集合类型,可以通过迭代器模式提供统一的遍历接口。
  2. 数据库查询结果:
    • 数据库查询结果可以封装为一个聚合对象,并提供迭代器遍历查询结果。
  3. 文件系统遍历:
    • 文件系统中的目录和文件可以封装为一个聚合对象,并提供迭代器遍历文件系统。
  4. 树形结构遍历:
    • 树形结构(如二叉树、多叉树)可以提供多种遍历方式(如深度优先、广度优先)。

假设我们有一个集合类 BookCollection,它包含多本书。我们需要实现一个迭代器,用于遍历这个集合。

package main

import "fmt"

// Book 定义书籍结构体
type Book struct {
  Title  string
  Author string
}

// Iterator 定义迭代器接口
type Iterator interface {
  HasNext() bool
  Next() *Book
}

// BookIterator 是具体迭代器
type BookIterator struct {
  books    []*Book
  position int
}

func (b *BookIterator) HasNext() bool {
  return b.position < len(b.books)
}

func (b *BookIterator) Next() *Book {
  if b.HasNext() {
    book := b.books[b.position]
    b.position++
    return book
  }
  return nil
}

// Aggregate 定义聚合接口
type Aggregate interface {
  CreateIterator() Iterator
}

// BookCollection 是具体聚合
type BookCollection struct {
  books []*Book
}

func (b *BookCollection) CreateIterator() Iterator {
  return &BookIterator{books: b.books}
}

func main() {
  // 创建书籍集合
  collection := &BookCollection{
    books: []*Book{
      {Title: "The Go Programming Language", Author: "Alan A. A. Donovan"},
      {Title: "Design Patterns", Author: "Erich Gamma"},
      {Title: "Clean Code", Author: "Robert C. Martin"},
    },
  }

  // 创建迭代器
  iterator := collection.CreateIterator()

  // 遍历集合
  for iterator.HasNext() {
    book := iterator.Next()
    fmt.Printf("Title: %s, Author: %s\n", book.Title, book.Author)
  }
}

中介者模式 Mediator

它通过引入一个中介者对象来封装一组对象之间的交互。中介者模式的核心思想是 将对象之间的复杂交互集中到一个中介者对象中,从而减少对象之间的直接耦合。

假设我们有一个聊天室系统,用户(同事类)之间通过聊天室(中介者)发送消息。用户之间不直接通信,而是通过聊天室来转发消息。

package main

import "fmt"

// Mediator 定义中介者接口
type Mediator interface {
  SendMessage(message string, sender Colleague)
}

// ChatRoom 是具体中介者
type ChatRoom struct {
  users []Colleague
}

func (c *ChatRoom) Register(user Colleague) {
  c.users = append(c.users, user)
}

func (c *ChatRoom) SendMessage(message string, sender Colleague) {
  for _, user := range c.users {
    if user != sender {
      user.Receive(message)
    }
  }
}

// Colleague 定义同事类接口
type Colleague interface {
  Send(message string)
  Receive(message string)
}

// User 是具体同事类
type User struct {
  name     string
  mediator Mediator
}

func (u *User) Send(message string) {
  fmt.Printf("%s sends: %s\n", u.name, message)
  u.mediator.SendMessage(message, u)
}

func (u *User) Receive(message string) {
  fmt.Printf("%s receives: %s\n", u.name, message)
}

func main() {
  // 创建中介者
  chatRoom := &ChatRoom{}

  // 创建用户
  alice := &User{name: "Alice", mediator: chatRoom}
  bob := &User{name: "Bob", mediator: chatRoom}
  charlie := &User{name: "Charlie", mediator: chatRoom}

  // 注册用户到聊天室
  chatRoom.Register(alice)
  chatRoom.Register(bob)
  chatRoom.Register(charlie)

  // 用户发送消息
  alice.Send("Hello, everyone!")
  bob.Send("Hi, Alice!")
  charlie.Send("Hey, Bob!")
}

中介者模式的优点

  1. 减少耦合:
    • 对象之间不直接通信,而是通过中介者进行交互,减少了对象之间的耦合。
  2. 集中控制:
    • 将对象之间的交互逻辑集中到中介者中,便于维护和扩展。
  3. 简化对象职责:
    • 每个对象只需要关注自己的行为,而不需要知道其他对象的存在。

备忘录模式 Memento

它允许在不破坏封装性的前提下,捕获并外部化一个对象的内部状态,以便稍后可以将该对象恢复到之前的状态。备忘录模式的核心思想是 将对象的状态保存到一个备忘录对象中,并在需要时从备忘录中恢复状态。

package main

import "fmt"

// Memento 定义备忘录接口
type Memento interface {
  GetState() string
}

// TextMemento 是具体备忘录
type TextMemento struct {
  state string
}

func (t *TextMemento) GetState() string {
  return t.state
}

// Originator 是原发器
type Originator struct {
  state string
}

func (o *Originator) SetState(state string) {
  o.state = state
}

func (o *Originator) GetState() string {
  return o.state
}

func (o *Originator) CreateMemento() Memento {
  return &TextMemento{state: o.state}
}

func (o *Originator) RestoreMemento(m Memento) {
  o.state = m.GetState()
}

// Caretaker 是管理者
type Caretaker struct {
  mementos []Memento
}

func (c *Caretaker) AddMemento(m Memento) {
  c.mementos = append(c.mementos, m)
}

func (c *Caretaker) GetMemento(index int) Memento {
  if index < len(c.mementos) {
    return c.mementos[index]
  }
  return nil
}

func main() {
  // 创建原发器
  editor := &Originator{}

  // 创建管理者
  caretaker := &Caretaker{}

  // 编辑文本并保存状态
  editor.SetState("State 1")
  caretaker.AddMemento(editor.CreateMemento()) // 创建一个备忘录对象,保存当前状态。

  editor.SetState("State 2")
  caretaker.AddMemento(editor.CreateMemento())

  editor.SetState("State 3")
  caretaker.AddMemento(editor.CreateMemento())

  fmt.Println(editor.GetState()) // 这个时候的状态已经是  State 3

  // 恢复到之前的状态
  editor.RestoreMemento(caretaker.GetMemento(1))
  fmt.Println("Restored State:", editor.GetState()) // 输出: Restored State: State 2

  editor.RestoreMemento(caretaker.GetMemento(0))
  fmt.Println("Restored State:", editor.GetState()) // 输出: Restored State: State 1
}

这种方式非常适合需要支持撤销操作或状态管理的场景,比如文本编辑器、游戏存档、事务回滚等。在 Go 语言中,备忘录模式可以通过接口和结构体的组合来实现,代码简洁且易于扩展。

观察者模式 Observer

它定义了对象之间的一对多依赖关系。当一个对象(被观察者)的状态发生改变时,所有依赖它的对象(观察者)都会收到通知并自动更新。

观察者模式的核心思想是 解耦被观察者和观察者,使得它们可以独立变化。

假设我们有一个天气站(被观察者),当天气数据更新时,通知多个显示设备(观察者)更新显示内容。

package main

import "fmt"

// Observer 定义观察者接口
type Observer interface {
  Update(temperature float64, humidity float64, pressure float64)
}

// Subject 定义被观察者接口
type Subject interface {
  RegisterObserver(o Observer)
  RemoveObserver(o Observer)
  NotifyObservers()
}

// WeatherData 是具体被观察者
type WeatherData struct {
  observers   []Observer
  temperature float64
  humidity    float64
  pressure    float64
}

func (w *WeatherData) RegisterObserver(o Observer) {
  w.observers = append(w.observers, o)
}

func (w *WeatherData) RemoveObserver(o Observer) {
  for i, observer := range w.observers {
    if observer == o {
      w.observers = append(w.observers[:i], w.observers[i+1:]...)
      break
    }
  }
}

func (w *WeatherData) NotifyObservers() {
  for _, observer := range w.observers {
    observer.Update(w.temperature, w.humidity, w.pressure)
  }
}

func (w *WeatherData) SetMeasurements(temperature float64, humidity float64, pressure float64) {
  w.temperature = temperature
  w.humidity = humidity
  w.pressure = pressure
  w.NotifyObservers()
}

// DisplayDevice 是具体观察者
type DisplayDevice struct {
  name string
}

func (d *DisplayDevice) Update(temperature float64, humidity float64, pressure float64) {
  fmt.Printf("%s: Temperature=%.2f, Humidity=%.2f, Pressure=%.2f\n", d.name, temperature, humidity, pressure)
}

func main() {
  // 创建被观察者
  weatherData := &WeatherData{}

  // 创建观察者
  display1 := &DisplayDevice{name: "Display 1"}
  display2 := &DisplayDevice{name: "Display 2"}

  // 注册观察者
  weatherData.RegisterObserver(display1)
  weatherData.RegisterObserver(display2)

  // 更新天气数据并通知观察者
  weatherData.SetMeasurements(25.0, 65.0, 1013.0)
  weatherData.SetMeasurements(26.5, 70.0, 1012.5)

  // 移除一个观察者
  weatherData.RemoveObserver(display1)

  // 再次更新天气数据
  weatherData.SetMeasurements(27.0, 68.0, 1011.0)
}

观察者模式的优点

  1. 解耦:
    • 被观察者和观察者之间是松耦合的,它们可以独立变化。
  2. 支持动态注册和移除观察者:
    • 可以在运行时动态地注册和移除观察者。
  3. 符合开闭原则:
    • 新增观察者时,不需要修改被观察者的代码。

状态模式 State

它允许对象在其内部状态改变时改变其行为。状态模式的核心思想是将对象的状态封装成独立的类,并将对象的行为委托给当前状态对象。

通过状态模式,可以将复杂的条件逻辑分散到多个状态类中,从而使得代码更加清晰和易于维护。

package main

import "fmt"

// State 定义状态接口
type State interface {
  PressSwitch(context *Context)
}

// Context 是上下文
type Context struct {
  state State
}

func (c *Context) SetState(state State) {
  c.state = state
}

func (c *Context) PressSwitch() {
  c.state.PressSwitch(c)
}

// OnState 是具体状态:开
type OnState struct{}

func (o *OnState) PressSwitch(context *Context) {
  fmt.Println("Turning the light off...")
  context.SetState(&OffState{})
}

// OffState 是具体状态:关
type OffState struct{}

func (o *OffState) PressSwitch(context *Context) {
  fmt.Println("Turning the light on...")
  context.SetState(&OnState{})
}

func main() {
  // 创建上下文并设置初始状态
  context := &Context{state: &OffState{}}

  // 按下开关
  context.PressSwitch() // 输出: Turning the light on...
  context.PressSwitch() // 输出: Turning the light off...
  context.PressSwitch() // 输出: Turning the light on...
}

状态模式的优点

  1. 清晰的状态转换逻辑:
    • 将状态转换逻辑分散到各个状态类中,避免了复杂的条件语句。
  2. 符合开闭原则:
    • 新增状态时,只需要添加新的状态类,而不需要修改现有代码。
  3. 易于扩展:
    • 可以轻松地添加新的状态和行为。

策略模式 Strategy

它定义了一系列算法,并将每个算法封装起来,使它们可以互相替换。策略模式的核心思想是将算法的使用与算法的实现分离,从而使得算法可以独立于客户端而变化。

通过策略模式,可以在运行时动态地选择算法,而不需要修改客户端代码。

假设我们有一个支付系统,支持多种支付方式(如信用卡支付、支付宝支付、微信支付等)。我们可以使用策略模式来实现支付方式的选择。

package main

import "fmt"

// PaymentStrategy 定义策略接口
type PaymentStrategy interface {
  Pay(amount float64)
}

// CreditCardPayment 是具体策略:信用卡支付
type CreditCardPayment struct{}

func (c *CreditCardPayment) Pay(amount float64) {
  fmt.Printf("Paying %.2f using Credit Card\n", amount)
}

// AlipayPayment 是具体策略:支付宝支付
type AlipayPayment struct{}

func (a *AlipayPayment) Pay(amount float64) {
  fmt.Printf("Paying %.2f using Alipay\n", amount)
}

// WechatPayment 是具体策略:微信支付
type WechatPayment struct{}

func (w *WechatPayment) Pay(amount float64) {
  fmt.Printf("Paying %.2f using Wechat Pay\n", amount)
}

// PaymentContext 是上下文
type PaymentContext struct {
  strategy PaymentStrategy
}

func (p *PaymentContext) SetStrategy(strategy PaymentStrategy) {
  p.strategy = strategy
}

func (p *PaymentContext) ExecutePayment(amount float64) {
  p.strategy.Pay(amount)
}

func main() {
  // 创建上下文
  context := &PaymentContext{}

  // 使用信用卡支付
  context.SetStrategy(&CreditCardPayment{})
  context.ExecutePayment(100.0) // 输出: Paying 100.00 using Credit Card

  // 使用支付宝支付
  context.SetStrategy(&AlipayPayment{})
  context.ExecutePayment(200.0) // 输出: Paying 200.00 using Alipay

  // 使用微信支付
  context.SetStrategy(&WechatPayment{})
  context.ExecutePayment(300.0) // 输出: Paying 300.00 using Wechat Pay
}

策略模式的优点

  1. 解耦:
    • 将算法的使用与算法的实现分离,使得算法可以独立于客户端而变化。
  2. 易于扩展:
    • 新增算法时,只需要添加新的策略类,而不需要修改现有代码。
  3. 动态选择算法:
    • 可以在运行时动态地选择算法,而不需要修改客户端代码。

实际应用场景

  1. 支付系统:
    • 如示例所示,支持多种支付方式。
  2. 排序算法:
    • 支持多种排序算法(如快速排序、归并排序、冒泡排序等)。
  3. 压缩算法:
    • 支持多种压缩算法(如 ZIP、RAR、7z 等)。
  4. 路由算法:
    • 支持多种路由算法(如最短路径、最快路径、最少收费路径等)。

模板方法模式 Template Method

它定义了一个算法的骨架,并将某些步骤延迟到子类中实现。模板方法模式的核心思想是 将算法的通用部分放在父类中,而将可变部分交给子类实现。

通过模板方法模式,可以避免代码重复,并确保算法的结构不变。

假设我们有一个饮料制作系统,支持制作咖啡和茶。咖啡和茶的制作过程有一些共同的步骤(如烧水、倒入杯子),也有一些不同的步骤(如冲泡咖啡、泡茶)。

package main

import "fmt"

// Beverage 是抽象类,定义饮料制作的模板方法
type Beverage interface {
  BoilWater()           // 烧水
  Brew()                // 冲泡
  PourInCup()           // 倒入杯子
  AddCondiments()       // 添加调料
  WantCondiments() bool // 是否添加调料(钩子方法)
}

// MakeBeverage 是模板方法
func MakeBeverage(b Beverage) {
  b.BoilWater()
  b.Brew()
  b.PourInCup()
  if b.WantCondiments() {
    b.AddCondiments()
  }
}

// Coffee 是具体类:咖啡
type Coffee struct{}

func (c *Coffee) BoilWater() {
  fmt.Println("Boiling water")
}

func (c *Coffee) Brew() {
  fmt.Println("Brewing coffee")
}

func (c *Coffee) PourInCup() {
  fmt.Println("Pouring coffee into cup")
}

func (c *Coffee) AddCondiments() {
  fmt.Println("Adding sugar and milk")
}

func (c *Coffee) WantCondiments() bool {
  return true
}

// Tea 是具体类:茶
type Tea struct{}

func (t *Tea) BoilWater() {
  fmt.Println("Boiling water")
}

func (t *Tea) Brew() {
  fmt.Println("Steeping tea")
}

func (t *Tea) PourInCup() {
  fmt.Println("Pouring tea into cup")
}

func (t *Tea) AddCondiments() {
  fmt.Println("Adding lemon")
}

func (t *Tea) WantCondiments() bool {
  return false
}

func main() {
  // 制作咖啡
  fmt.Println("Making coffee:")
  coffee := &Coffee{}
  MakeBeverage(coffee)

  // 制作茶
  fmt.Println("\nMaking tea:")
  tea := &Tea{}
  MakeBeverage(tea)
}

模板方法模式的优点

  1. 代码复用:
    • 将算法的通用部分放在父类中,避免了代码重复。
  2. 扩展性:
    • 新增子类时,只需要实现抽象方法,而不需要修改父类的代码。
  3. 控制算法结构:
    • 父类控制算法的结构,确保算法的步骤不会被子类改变。

实际应用场景

  1. 框架设计:
    • 如 Web 框架中的请求处理流程(初始化、处理请求、返回响应等)。
  2. 工作流引擎:
    • 如订单处理流程(创建订单、支付订单、发货订单等)。
  3. 测试框架:
    • 如测试用例的执行流程(初始化、执行测试、清理资源等)。
  4. 游戏开发:
    • 如游戏角色的行为流程(移动、攻击、防御等)。

访问者模式 Visitor

它允许你将算法与对象结构分离。访问者模式的核心思想是 将操作(算法)从对象结构中分离出来,使得可以在不修改对象结构的情况下定义新的操作。

通过访问者模式,可以将对象结构的元素与作用于这些元素的操作解耦,从而使得操作可以独立变化。

假设我们有一个文档对象模型(DOM),包含多种元素(如文本、图片、表格等)。我们需要对这些元素进行不同的操作(如导出为 PDF、导出为 HTML 等)。

package main

import "fmt"

// Visitor 定义访问者接口
type Visitor interface {
  VisitText(text *Text)
  VisitImage(image *Image)
  VisitTable(table *Table)
}

// Element 定义元素接口
type Element interface {
  Accept(visitor Visitor)
}

// Text 是具体元素:文本
type Text struct {
  content string
}

func (t *Text) Accept(visitor Visitor) {
  visitor.VisitText(t)
}

// Image 是具体元素:图片
type Image struct {
  src string
}

func (i *Image) Accept(visitor Visitor) {
  visitor.VisitImage(i)
}

// Table 是具体元素:表格
type Table struct {
  rows int
  cols int
}

func (t *Table) Accept(visitor Visitor) {
  visitor.VisitTable(t)
}

// PDFExportVisitor 是具体访问者:导出为 PDF
type PDFExportVisitor struct{}

func (p *PDFExportVisitor) VisitText(text *Text) {
  fmt.Printf("Exporting text to PDF: %s\n", text.content)
}

func (p *PDFExportVisitor) VisitImage(image *Image) {
  fmt.Printf("Exporting image to PDF: %s\n", image.src)
}

func (p *PDFExportVisitor) VisitTable(table *Table) {
  fmt.Printf("Exporting table to PDF: %d rows, %d cols\n", table.rows, table.cols)
}

// HTMLExportVisitor 是具体访问者:导出为 HTML
type HTMLExportVisitor struct{}

func (h *HTMLExportVisitor) VisitText(text *Text) {
  fmt.Printf("Exporting text to HTML: %s\n", text.content)
}

func (h *HTMLExportVisitor) VisitImage(image *Image) {
  fmt.Printf("Exporting image to HTML: %s\n", image.src)
}

func (h *HTMLExportVisitor) VisitTable(table *Table) {
  fmt.Printf("Exporting table to HTML: %d rows, %d cols\n", table.rows, table.cols)
}

// Document 是对象结构
type Document struct {
  elements []Element
}

func (d *Document) AddElement(element Element) {
  d.elements = append(d.elements, element)
}

func (d *Document) Accept(visitor Visitor) {
  for _, element := range d.elements {
    element.Accept(visitor)
  }
}

func main() {
  // 创建文档对象
  document := &Document{}

  // 添加元素
  document.AddElement(&Text{content: "Hello, World!"})
  document.AddElement(&Image{src: "image.png"})
  document.AddElement(&Table{rows: 3, cols: 2})

  // 创建访问者
  pdfVisitor := &PDFExportVisitor{}
  htmlVisitor := &HTMLExportVisitor{}

  // 导出为 PDF
  fmt.Println("Exporting to PDF:")
  document.Accept(pdfVisitor)

  // 导出为 HTML
  fmt.Println("\nExporting to HTML:")
  document.Accept(htmlVisitor)
}

访问者模式的优点

  1. 解耦:
    • 将操作与对象结构分离,使得操作可以独立变化。
  2. 扩展性:
    • 新增操作时,只需要添加新的访问者,而不需要修改对象结构。
  3. 符合开闭原则:
    • 对象结构对扩展开放,对修改关闭。