Go:函数式还是面向对象?选择编程风格

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

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

开始课程

引言:Go世界中的永恒争论

\n\n

Go——一门打破常规的语言。它不容忍过度复杂,却给予开发者难以置信的自由。每个开始用Go编程(或从Python、Java转来)的人,迟早都会遇到一个难题:“到底怎样才是正确的?是像Java那样用类和继承,还是像Haskell那样用无副作用的函数?”

\n\n

剧透:Go既不是纯粹的面向对象语言,也不是纯粹的函数式语言。它很务实。但这并不意味着你不能(也不应该)同时使用这两种范式。在本文中,我们将探讨如何在Go中结合函数式与面向对象方法,使你的代码更具可读性、可测试性和高效性。我们不会进行说教,而是通过实际示例和反模式来学习。

\n\n

如果你正在寻找Go的最佳实践,并希望了解如何用这门语言编写整洁的代码,那么你来对地方了。

\n\n

1. Go中的面向对象方法:没有类,但有灵魂

\n\n

许多人认为,既然Go中没有class,那么它就没有面向对象编程。这是一个误解。Go中的面向对象编程建立在两大支柱上:结构体(structs)和接口(interfaces)。没有继承,但有组合,语言的设计者认为组合更加灵活。

\n\n

结构体与方法

\n

你可以为任何类型定义方法。这就是面向对象代码的基础。

\n\n
package main\n\nimport "fmt"\n\n// 定义接口(契约)\ntype Notifier interface {\n    Send(message string) error\n}\n\n// 实现接口的结构体\ntype EmailNotifier struct {\n    Email string\n}\n\n// EmailNotifier的方法\nfunc (e EmailNotifier) Send(message string) error {\n    fmt.Printf("发送邮件至 %s: %s\\n", e.Email, message)\n    return nil\n}\n\n// 另一个结构体\ntype SMSNotifier struct {\n    Phone string\n}\n\nfunc (s SMSNotifier) Send(message string) error {\n    fmt.Printf("发送短信至 %s: %s\\n", s.Phone, message)\n    return nil\n}\n\n// 接受任何Notifier的函数(多态)\nfunc AlertAll(notifiers []Notifier, msg string) {\n    for _, n := range notifiers {\n        n.Send(msg)\n    }\n}\n\nfunc main() {\n    notifiers := []Notifier{\n        EmailNotifier{Email: "user@example.com"},\n        SMSNotifier{Phone: "+1234567890"},\n    }\n    AlertAll(notifiers, "服务器宕机!")\n}
\n\n

Go中面向对象的优点:

\n
    \n
  • 通过接口实现多态。你只需编写一次AlertAll函数,它就能与任何实现了Notifier的类型一起工作。这是松耦合。
  • \n
  • 状态管理。结构体非常适合数据模型(User, Order, Config)。
  • \n
  • 封装。小写字母开头的字段是私有的,大写字母开头的是公有的。简单而高效。
  • \n
\n\n

缺点:

\n
    \n
  • 容易滑向“Java风格”:创建深层层次结构,尽管Go并不支持。
  • \n
  • 可变性(状态改变)使测试和调试变得复杂。
  • \n
\n\n

2. Go中的函数式编程:为了速度而追求纯粹

\n\n

Go中的函数式风格并非指lambda演算,而是一套原则:最小化副作用、不可变性、函数作为参数。随着泛型(Go 1.18+)的出现,这种风格变得更加强大。

\n\n

纯函数与不可变性

\n

函数式编程的主要规则:函数接收数据,返回新数据,但不改变外部任何内容。

\n\n
package main\n\nimport "fmt"\n\n// 不好:修改传入的切片(副作用)\nfunc BadAddPrefix(users []string, prefix string) {\n    for i := range users {\n        users[i] = prefix + users[i]\n    }\n}\n\n// 好:返回一个新的切片(纯函数)\                

博客

书籍推荐