essay

Go的设计模式

#architecture

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)

用于封装对象的创建逻辑,客户端无需关心具体创建的是哪个结构体。

  1. 简单工厂模式:使用字符串 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 元
}
  1. 工厂方法
    不再使用一个统一的工厂函数来通过 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 元
}
  1. 抽象工厂模式
    用于创建一系列相关或依赖的对象,工厂不再是生产单一产品,而是生产产品族,例如支付和退款。
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")
}
comments如果有不同意见或者补充,直接留在这里。
contact

在别处继续找到我

如果你想聊技术、设计,或者只是打个招呼。

暂未配置外部链接