Go的设计模式
Go中的设计模式
在 Go 语言中应用设计模式,核心思想与 Java 等传统面向对象语言有显著不同。Go 的设计哲学强调简洁性、组合优于继承,并利用其独特的并发模型。因此,许多经典的设计模式在 Go 中要么被大幅简化,要么被更惯用的方式所取代。
简单来说,在 Go 中应“少想模式,多想简单”。
💡 核心理念:Go 如何简化经典模式
| 经典模式 | Go 中的简化实现 | 原因 |
|---|---|---|
| 单例模式 (Singleton) | 包级变量 + sync.Once |
Go 的包在导入时即初始化,全局变量天然存在。sync.Once 可轻松保证并发安全。 |
| 工厂模式 (Factory) | 返回接口的普通函数 | 无需专门的工厂类。一个函数根据参数返回实现了某个接口的不同结构体即可。 |
| 策略模式 (Strategy) | 函数类型 (Function Type) | 可以将函数本身作为参数传递,无需定义一堆策略接口和实现类。 |
| 装饰器模式 (Decorator) | 结构体嵌入 (Embedding) | 通过嵌入一个接口类型的字段,可以方便地为对象添加功能,是组合的直接体现。 |
| 观察者模式 (Observer) | channel + goroutine |
使用 channel 进行消息广播和订阅,是 Go 中处理事件和通知最自然、高效的方式。 |
️ Go 中常用且惯用的模式
相比于生搬硬套 23 种经典模式,以下是在 Go 项目中更常见、更符合其哲学的设计选择。
单例模式 (Singleton)
确保一个类型只有一个实例,并提供全局访问点。在 Go 中,这通常用于日志记录器、配置管理或数据库连接池。
核心实现:使用 sync.Once 保证在并发环境下的线程安全。
package main
import (
"fmt"
"sync"
)
type Logger struct {
name string
}
var (
instance *Logger
once sync.Once
)
func GetLogger() *Logger {
once.Do(func() {
instance = &Logger{name: "AppLogger"}
})
return instance
}
func main() {
logger1 := GetLogger()
logger2 := GetLogger()
fmt.Println("是同一个实例吗?", logger1 == logger2) // 输出: true
}工厂模式 (Factory)
用于封装对象的创建逻辑,客户端无需关心具体创建的是哪个结构体。
- 简单工厂模式:使用字符串
switch创建对象,不符合开闭原则
package main
import "fmt"
type Payment interface {
Pay(amount float64) string
}
type CreditCard struct{}
func (c *CreditCard) Pay(amount float64) string {
return fmt.Sprintf("用信用卡支付了 %.2f 元", amount)
}
type PayPal struct{}
func (p *PayPal) Pay(amount float64) string {
return fmt.Sprintf("用 PayPal 支付了 %.2f 元", amount)
}
// 工厂函数
func NewPayment(paymentType string) (Payment, error) {
switch paymentType {
case "credit":
return &CreditCard{}, nil
case "paypal":
return &PayPal{}, nil
default:
return nil, fmt.Errorf("未知的支付类型: %s", paymentType)
}
}
func main() {
p, _ := NewPayment("credit")
fmt.Println(p.Pay(100.50)) // 输出: 用信用卡支付了 100.50 元
}- 工厂方法
不再使用一个统一的工厂函数来通过 switch 判断创建对象,而是定义一个工厂接口。每种支付方式都有自己专属的工厂结构体。
这种方式符合开闭原则:如果我们要新增一种支付方式(比如 UnionPay),只需要新增一个结构体和一个工厂结构体,而不需要修改现有的任何代码。
package main
import "fmt"
// --- 1. 抽象产品 ---
type Payment interface {
Pay(amount float64) string
}
// --- 2. 具体产品 ---
type CreditCard struct{}
func (c *CreditCard) Pay(amount float64) string {
return fmt.Sprintf("用信用卡支付了 %.2f 元", amount)
}
type PayPal struct{}
func (p *PayPal) Pay(amount float64) string {
return fmt.Sprintf("用 PayPal 支付了 %.2f 元", amount)
}
// --- 3. 抽象工厂 ---
type PaymentFactory interface {
CreatePayment() Payment
}
// --- 4. 具体工厂 ---
type CreditCardFactory struct{}
func (cf *CreditCardFactory) CreatePayment() Payment {
return &CreditCard{}
}
type PayPalFactory struct{}
func (pf *PayPalFactory) CreatePayment() Payment {
return &PayPal{}
}
// --- 5. 客户端调用 ---
func main() {
// 场景 A: 使用信用卡工厂
var factory PaymentFactory = &CreditCardFactory{}
payment1 := factory.CreatePayment()
fmt.Println(payment1.Pay(100.50)) // 输出: 用信用卡支付了 100.50 元
// 场景 B: 使用 PayPal 工厂
factory = &PayPalFactory{}
payment2 := factory.CreatePayment()
fmt.Println(payment2.Pay(200.00)) // 输出: 用 PayPal 支付了 200.00 元
}- 抽象工厂模式
用于创建一系列相关或依赖的对象,工厂不再是生产单一产品,而是生产产品族,例如支付和退款。
package main
import "fmt"
// --- 1. 产品族接口 ---
type Payment interface {
Pay(amount float64) string
}
type Refund interface {
Refund(amount float64) string
}
// --- 2. 具体产品族 A:信用卡 ---
type CreditCardPayment struct{}
func (c *CreditCardPayment) Pay(amount float64) string {
return fmt.Sprintf("[信用卡] 支付 %.2f 元", amount)
}
type CreditCardRefund struct{}
func (c *CreditCardRefund) Refund(amount float64) string {
return fmt.Sprintf("[信用卡] 退款 %.2f 元", amount)
}
// --- 3. 具体产品族 B:PayPal ---
type PayPalPayment struct{}
func (p *PayPalPayment) Pay(amount float64) string {
return fmt.Sprintf("[PayPal] 支付 %.2f 元", amount)
}
type PayPalRefund struct{}
func (p *PayPalRefund) Refund(amount float64) string {
return fmt.Sprintf("[PayPal] 退款 %.2f 元", amount)
}
// --- 4. 抽象工厂接口 ---
type PaymentFactory interface {
CreatePayment() Payment
CreateRefund() Refund
}
// --- 5. 具体工厂实现 ---
type CreditCardFactory struct{}
func (cf *CreditCardFactory) CreatePayment() Payment {
return &CreditCardPayment{}
}
func (cf *CreditCardFactory) CreateRefund() Refund {
return &CreditCardRefund{}
}
type PayPalFactory struct{}
func (pf *PayPalFactory) CreatePayment() Payment {
return &PayPalPayment{}
}
func (pf *PayPalFactory) CreateRefund() Refund {
return &PayPalRefund{}
}
// --- 6. 客户端调用 ---
func main() {
// 假设系统配置选择了 PayPal 渠道
var factory PaymentFactory = &PayPalFactory{}
// 客户端通过工厂获取一整套服务
payService := factory.CreatePayment()
refundService := factory.CreateRefund()
fmt.Println(payService.Pay(500.00)) // 输出: [PayPal] 支付 500.00 元
fmt.Println(refundService.Refund(500.00)) // 输出: [PayPal] 退款 500.00 元
}函数式选项模式 (Functional Options Pattern)
这是 Go 中处理带有大量可选参数的结构体初始化的首选方式,比建造者模式更简洁、更灵活。
核心实现:定义一个 Option 函数类型,并将其作为可变参数传入构造函数。
package main
import (
"fmt"
"time"
)
type Server struct {
host string
port int
timeout time.Duration
maxConns int
}
// Option 函数类型
type ServerOption func(*Server)
// 具体的选项函数
func WithTimeout(t time.Duration) ServerOption {
return func(s *Server) {
s.timeout = t
}
}
func WithMaxConns(m int) ServerOption {
return func(s *Server) {
s.maxConns = m
}
}
// 构造函数
func NewServer(host string, port int, opts ...ServerOption) *Server {
s := &Server{
host: host,
port: port,
// 设置默认值
timeout: 30 * time.Second,
maxConns: 100,
}
// 应用所有选项
for _, opt := range opts {
opt(s)
}
return s
}
func main() {
// 创建服务器实例,使用了两个选项来覆盖默认值
srv := NewServer("localhost", 8080, WithTimeout(1*time.Minute), WithMaxConns(500))
// 注意:原代码中是 %vn (字母v和字母n),这里修正为 %v\n (换行符) 以便输出更美观
fmt.Printf("服务器: %v\n", srv)
// 输出: 服务器: &{localhost 8080 1m0s 500}
}策略模式
核心实现:定义一系列算法,并将它们封装起来,使它们可以互相替换
下面是一个经典的“支付场景”示例:我们定义了不同的支付策略(微信支付、支付宝、信用卡),客户端可以根据需要动态切换策略,而无需修改核心代码。
package main
import "fmt"
// --- 1. 策略接口 (Strategy Interface) ---
// 定义所有支付方式必须遵守的标准
type PaymentStrategy interface {
Pay(amount float64) string
}
// --- 2. 具体策略 (Concrete Strategies) ---
// 策略 A:微信支付
type WeChatPay struct{}
func (w WeChatPay) Pay(amount float64) string {
return fmt.Sprintf("使用 [微信支付] 支付了 %.2f 元", amount)
}
// 策略 B:支付宝
type Alipay struct{}
func (a Alipay) Pay(amount float64) string {
return fmt.Sprintf("使用 [支付宝] 支付了 %.2f 元", amount)
}
// 策略 C:信用卡
type CreditCard struct{}
func (c CreditCard) Pay(amount float64) string {
return fmt.Sprintf("使用 [信用卡] 刷卡支付了 %.2f 元", amount)
}
// --- 3. 上下文 (Context) ---
// 持有一个策略接口的引用,负责调用策略
type ShoppingCart struct {
paymentMethod PaymentStrategy
}
// 设置或切换支付策略
func (cart *ShoppingCart) SetPaymentMethod(method PaymentStrategy) {
cart.paymentMethod = method
}
// 执行支付
func (cart *ShoppingCart) Checkout(amount float64) {
fmt.Println(cart.paymentMethod.Pay(amount))
}
// --- 4. 客户端调用 ---
func main() {
// 创建一个购物车(上下文)
cart := &ShoppingCart{}
// 场景 A: 用户选择微信支付
cart.SetPaymentMethod(&WeChatPay{})
cart.Checkout(100.00)
// 输出: 使用 [微信支付] 支付了 100.00 元
// 场景 B: 用户改变主意,切换成支付宝
cart.SetPaymentMethod(&Alipay{})
cart.Checkout(200.50)
// 输出: 使用 [支付宝] 支付了 200.50 元
// 场景 C: 用户最后决定用信用卡
cart.SetPaymentMethod(&CreditCard{})
cart.Checkout(500.00)
// 输出: 使用 [信用卡] 刷卡支付了 500.00 元
}装饰器模式 (Decorator)
动态地给对象添加职责。Go 的组合特性使其实现非常自然。
核心实现:通过结构体嵌入接口,在调用前后增加额外逻辑。
package main
import (
"fmt"
"time"
)
type Logger interface {
Log(msg string)
}
type ConsoleLogger struct{}
func (c ConsoleLogger) Log(msg string) {
fmt.Println(msg)
}
// 装饰器
type TimedLogger struct {
Logger // 嵌入 Logger 接口,持有被装饰的对象
}
func (t TimedLogger) Log(msg string) {
// 添加额外功能:打印时间
t1 := time.Now()
t.Logger.Log(msg) // 调用被装饰对象的方法
sub := time.Since(t1)
fmt.Println("耗时:", sub)
}
func main() {
// 1. 创建基础对象
baseLogger := ConsoleLogger{}
// 2. 用装饰器包装它
timedLogger := TimedLogger{baseLogger}
// 3. 调用装饰后的方法
timedLogger.Log("这是一条带时间的日志")
// 输出:
// 这是一条带时间的日志
// 耗时: 29.625µs
}观察者模式(Observer)
观察者模式本质上就是发布-订阅机制。
发布者(被观察者):负责产生事件或状态变化。它只管广播消息,完全不关心谁订阅了它,也不关心订阅者收到消息后会做什么。
订阅者(观察者):负责接收消息。它们被动等待,一旦收到通知,就执行自己的业务逻辑。
package main
import "fmt"
// --- 1. 定义角色接口 ---
// Observer (观察者接口)
// 定义了观察者必须拥有的“接收更新”的能力
type Observer interface {
Receive(int) // 接收温度数据
}
// Subject (主题/被观察者接口)
// 定义了气象台必须具备的功能:发送数据、通知大家、注册观察者
type Subject interface {
Send(int) // 设置新温度并触发通知
Notify() // 通知所有观察者
Register(...Observer) // 接纳新的观察者
}
// --- 2. 具体主题 (Concrete Subject) ---
// WeatherStation (具体的气象台)
type WeatherStation struct {
Observers []Observer // 存放所有订阅了天气的用户(观察者列表)
tem int // 当前的温度值
}
// --- 3. 具体观察者 (Concrete Observer) ---
// User (具体的用户)
type User struct {
Name string
}
// 实现 Observer 接口
// 当用户接收到温度变化时,打印出来
func (u User) Receive(tem int) {
fmt.Println(u.Name+"这里温度变为:", tem)
}
// --- 4. 实现主题的具体逻辑 ---
// Send (发送数据)
// 1. 更新自己的内部状态 (温度)
// 2. 调用 Notify 通知所有人
func (w *WeatherStation) Send(tem int) {
w.tem = tem // 更新温度
w.Notify() // 触发通知
}
// Notify (通知)
// 遍历所有注册的观察者,挨个调用他们的 Receive 方法
func (w *WeatherStation) Notify() {
for _, observer := range w.Observers {
observer.Receive(w.tem) // 把最新的温度告诉观察者
}
}
// Register (注册)
// 允许一次性注册多个观察者(可变参数 ...Observer)
func (w *WeatherStation) Register(observer ...Observer) {
for _, o := range observer {
w.Observers = append(w.Observers, o) // 加入观察列表
}
}
// --- 5. 客户端调用 ---
func main() {
// 1. 创建观察者(用户)
zhangsan := User{Name: "zhangsan"}
lisi := User{Name: "lisi"}
// 2. 创建主题(气象台)
var weatherStation Subject = &WeatherStation{}
// 3. 用户订阅天气(注册观察者)
weatherStation.Register(zhangsan, lisi)
// 4. 气象台发布新温度(触发更新)
weatherStation.Send(20) // 输出: zhangsan这里温度变为:20 / lisi这里温度变为:20
weatherStation.Send(22) // 输出: zhangsan这里温度变为:22 / lisi这里温度变为:22
}
依赖注入 (Dependency Injection)
这是 Go 中实现解耦的基石。它不是一种特定的模式,而是一种通过构造函数传递依赖的设计实践。
核心实现:在创建结构体时,通过构造函数参数传入其依赖的接口。
package main
import "fmt"
// 定义依赖接口
type Storer interface {
Save(key string, val []byte) error
}
// 业务逻辑结构体
type UserService struct {
store Storer // 依赖一个 Storer
}
// 通过构造函数注入依赖
func NewUserService(s Storer) *UserService {
return &UserService{store: s}
}
func (s *UserService) CreateUser(name string) error {
// ... 业务逻辑
return s.store.Save("user_name", []byte(name))
}
// 一个具体的实现
type MemoryStorer struct{}
func (m *MemoryStorer) Save(key string, val []byte) error {
fmt.Printf("将 %s 存入内存: %sn", key, string(val))
return nil
}
func main() {
store := &MemoryStorer{}
service := NewUserService(store) // 注入依赖
service.CreateUser("Alice")
}