10 design patterns in go that help you to become a senior

控制依赖关系,而不仅仅是使用struct创建对象
// 构造函数对象
type UserService struct {
repo UserRepository
}
// 控制依赖关系,而不仅仅创建对象
func NewUserService(r UserRepository) *UserService {
return &UserService{repo: r}
}
初级开发者常常使用硬编码依赖,这降低了代码的可复用性,使用构造函数来优雅地注入依赖,是Go语言中推荐的做法。
保持接口精简,只暴露必要的内容
type EmailSender interface {
Send(to, subject, body string) error
}
为对象提供灵活可选的配置,无需定义多个构造函数。
type Product struct {
Name string
Price float64
}
type Option func(*Product)
func NewProduct(options ...Option) *Product {
p := &Product{
Name: "Default Product", // 默认值
Price: 10.0,
}
for _, option := range options {
option(p)
}
return p
}
func WithName(name string) Option {
return func(p *Product) {
p.Name = name
}
}
func WithPrice(price float64) Option {
return func(p *Product) {
p.Price = price
}
}
// usage
product := NewProduct(WithName("Laptop"), WithPrice(1200.50))
将业务逻辑与数据访问逻辑分离。
type UserRepository interface {
Get(ctx context.Context, id string) (*User, error)
Save(ctx context.Context, user *User) error
}
type UserService struct {
repo UserRepository
}
// 在 Service 中使用
func (s *UserService) GetUser(ctx context.Context, id string) (*User, error) {
return s.repo.Get(ctx, id)
}
为错误添加上下文信息,保留原始错误堆栈。
Go 1.13+ 推荐使用标准库的方式:
// 使用 fmt.Errorf 包装错误
err := connectDatabase()
if err != nil {
return fmt.Errorf("无法连接数据库: %w", err)
}
// 解包并检查特定错误
if errors.Is(err, sql.ErrNoRows) {
// 处理记录不存在的情况
}
// 提取特定类型的错误
var sqlErr *mysql.MySQLError
if errors.As(err, &sqlErr) {
// 处理 MySQL 特定错误
}
errors.Is / errors.As)在主要逻辑前后添加横切关注点(Cross-cutting concerns)。
type Middleware func(http.Handler) http.Handler
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("Starting Request: %s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
log.Printf("Finished Request: %s %s", r.Method, r.URL.Path)
})
}
// 链式中间件使用
// handler := LoggingMiddleware(AuthMiddleware(mainHandler))
传递请求范围的值、取消信号和截止时间。这是 Go 并发模式的核心。
// 创建带超时的 context
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// 在函数中使用 context
func ProcessRequest(ctx context.Context, data string) error {
select {
case <-ctx.Done():
return ctx.Err() // 超时或取消
case result := <-doWork(data):
return result
}
}
定义一系列算法,使它们可以互换。
type PaymentStrategy interface {
Pay(amount float64) error
}
type CreditCardPayment struct{}
type PayPalPayment struct{}
func (c *CreditCardPayment) Pay(amount float64) error {
fmt.Printf("Paying %.2f using Credit Card\n", amount)
return nil
}
func (p *PayPalPayment) Pay(amount float64) error {
fmt.Printf("Paying %.2f using PayPal\n", amount)
return nil
}
将不兼容的接口转换为客户端所期望的接口。
type OldSystem interface {
OldMethod() string
}
type NewSystem interface {
NewMethod() string
}
type Adapter struct {
old OldSystem
}
func (a *Adapter) NewMethod() string {
return a.old.OldMethod() // 适配旧接口到新接口
}
封装对象创建逻辑,根据类型生成具体实例。
type PaymentGateway interface {
Process(amount float64) error
}
type PayPalGateway struct{}
func (p *PayPalGateway) Process(amount float64) error { return nil }
type StripeGateway struct{}
func (s *StripeGateway) Process(amount float64) error { return nil }
const (
PayPal = iota
Stripe
)
func NewPaymentGateway(gatewayType int) PaymentGateway {
switch gatewayType {
case PayPal:
return &PayPalGateway{}
case Stripe:
return &StripeGateway{}
default:
return nil
}
}
| 方面 | 初级开发者 | 高级开发者 |
|---|---|---|
| 依赖管理 | 硬编码依赖 | 依赖注入 |
| 全局状态 | 使用全局变量 | 通过参数传递 |
| 测试性 | 不考虑测试 | 代码可测试 |
| 耦合度 | 紧耦合 | 松耦合、可维护 |