Go语言设计模式:何时以及为何使用它们

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

Go语言设计模式:不是“教条”,而是工具

\n\n

当我刚开始用Go编程时,总觉得那些“四人帮”的23种模式是Java和C++世界的东西。Go如此简单简洁——我们为什么需要这些复杂的东西?但随着我深入实际项目(微服务、CLI工具、API处理器),我越来越频繁地想到:“这里用工厂方法就好了”或“这堆if-else可以用策略模式替代”。

\n\n

Go语言设计模式不是教条,也不是试图将面向对象编程强加于函数式语言。它们是经过验证的典型问题解决方案。它们有助于使代码可测试、灵活且易于同事理解。是的,Go有其自身特点:由于缺乏经典的继承和泛型(直到1.18版本),一些模式会发生变化,而另一些则变得多余。

\n\n

在本文中,我们不会列举所有23种模式。相反,我们将解析Golang中最常用的5种模式,这些模式在实际项目中确实有用。您将看到何时应该使用它们,以及何时应该避而远之。

\n\n

Go中的单例模式:不仅仅是单例,而是Go风格的单例

\n\n

单例模式确保一个结构体只有一个实例。在Go中,这通常用于配置、连接池或日志记录器。但有一个细微差别:Go没有静态类,但有sync.Once——这是惰性初始化的理想工具。

\n\n

没有单例模式的问题

\n

假设您有一个Config结构体,它从文件中读取设置。如果每个包都创建自己的实例,您要么读取文件10次(速度慢),要么遇到竞态条件。

\n\n

使用单例模式的解决方案

\n
package config\n\nimport (\n    "sync"\n    "encoding/json"\n    "os"\n)\n\ntype Config struct {\n    Port int    `json:"port"`\n    Env  string `json:"env"`\n}\n\nvar (\n    instance *Config\n    once     sync.Once\n)\n\nfunc GetConfig() *Config {\n    once.Do(func() {\n        file, _ := os.Open("config.json")\n        defer file.Close()\n        instance = &Config{}\n        json.NewDecoder(file).Decode(instance)\n    })\n    return instance\n}
\n\n

何时使用:当需要保证实例的唯一性时(配置、缓存、工厂)。

\n

何时不用:不要什么都用。单例模式是一种全局状态,会使测试复杂化。如果可以通过构造函数传递依赖,那就传递。

\n\n

工厂模式:当对象创建变得复杂时

\n\n

工厂方法和抽象工厂模式是关于封装对象创建逻辑。在Go中,这尤其重要,因为我们没有传统意义上的构造函数。相反,我们使用以New为前缀的构造函数。

\n\n

示例:创建通知

\n

假设您的系统通过电子邮件、短信和推送发送通知。与其将选择逻辑分散在代码中,不如使用工厂。

\n\n
package notification\n\nimport "fmt"\n\ntype Notifier interface {\n    Send(message string) error\n}\n\ntype EmailNotifier struct{}\n\nfunc (e EmailNotifier) Send(message string) error {\n    fmt.Println("Sending email:", message)\n    return nil\n}\n\ntype SMSNotifier struct{}\n\nfunc (s SMSNotifier) Send(message string) error {\n    fmt.Println("Sending SMS:", message)\n    return nil\n}\n\nfunc NewNotifier(typ string) Notifier {\n    switch typ {\n    case "email":\n        return EmailNotifier{}\n    case "sms":\n        return SMSNotifier{}\n    default:\n        return nil\n    }\n}
\n\n

何时使用:当创建逻辑复杂时(依赖于类型、版本、配置)。

\n

何时不用:如果只有一种对象类型。直接写MyStruct{}或NewMyStruct()即可,无需工厂。

\n\n

策

博客

书籍推荐