GPT 解惑:以类为中心的 OOP(Java)➔ 以函数为中心(Go/Kotlin)的多范式编程思维转变
困扰:
我原是 Java 程序员,在学 Kotlin 和 Golang 后,我有点不知所措,突然不会编程了。 因为在 Java 中类是一等公民,先写类再在类中写方法, 而 Kotlin 和 Golang 中函数和类可以处于同一级别,函数和类都是一等公民, 那我在用它写代码时我不知道该怎样组织代码,不知道是直接写函数还是写类,先写类还是先写函数, 或者两者混合,怎样组合,谁多谁少等,比例原则,什么时候该直接写类,什么时候该直接写函数,很困惑。
ChatGPT:
这个问题非常好,说明你已经从“语法层面”进入到“编程思维层面”的转变阶段。你困惑的根源在于: ➡️ Java 是 面向对象为中心(OOP-centric) 的语言,而 Kotlin 和 Go 都是 多范式(multi-paradigm) 语言,它们支持 OOP,但不依赖于 OOP。
我们来一步步拆开讲。
一、Java 思维:一切都在类里
在 Java 世界中:
- 逻辑必须包装在类中(哪怕是工具类、静态方法)。
- “对象”和“继承”是设计的核心。
- 模块组织依赖于 class 层次结构。
比如:
class UserService {
public void register(User user) { ... }
}
即使 register
只是一个函数,也要为它找个类来“安放”。
二、Kotlin / Go 的思维转变
1. 顶层函数是正常的
在 Kotlin / Go 中,函数不必属于类,这是一个有意的设计。
这意味着你可以这样写:
fun register(user: User) { ... }
或者在 Go:
func Register(user User) { ... }
这样写没有问题,不“丑”,反而更清晰,因为函数才是逻辑的核心,而类/结构体只是组织数据的一种手段。
三、如何决定“写类”还是“写函数”?
✅ 1. 当逻辑围绕数据展开时,用类(或 struct)
例如:
- 你有一个概念明确的数据实体(User、Order、Session)。
- 方法的行为显然依赖于这个数据的内部状态。
Kotlin 示例:
data class User(val name: String, val email: String) {
fun sendEmail(message: String) {
println("Sending email to $email: $message")
}
}
Go 示例:
type User struct {
Name string
Email string
}
func (u *User) SendEmail(msg string) {
fmt.Printf("Sending email to %s: %s\n", u.Email, msg)
}
➡️ 特征:方法与数据“绑定”在一起,逻辑依附于结构。
✅ 2. 当逻辑只是过程或纯计算,用函数
当函数的逻辑:
- 不依赖某个对象的状态;
- 只是纯输入输出;
- 或者只是一个操作流程(例如文件处理、网络请求);
就直接写顶层函数。
Kotlin 示例:
fun readConfig(path: String): Config { ... }
fun main() {
val config = readConfig("config.yaml")
}
Go 示例:
func ReadConfig(path string) Config { ... }
func main() {
config := ReadConfig("config.yaml")
}
➡️ 特征:函数是独立逻辑单元,不需要类的上下文。
✅ 3. 当多个函数共享上下文或依赖同一资源时,用类 / struct 封装
当一组函数共享同一个依赖(例如数据库连接、HTTP client),那就应该放进类(Kotlin)或 struct(Go)。
Kotlin 示例:
class UserRepository(private val db: Database) {
fun findById(id: Int): User? = db.query(...)
fun save(user: User) = db.execute(...)
}
Go 示例:
type UserRepository struct {
db *sql.DB
}
func (r *UserRepository) FindByID(id int) (*User, error) { ... }
func (r *UserRepository) Save(u *User) error { ... }
➡️ 特征:共享依赖、生命周期一致、方法需要共享状态。
✅ 4. 当你需要抽象或多态,用接口(interface)
Kotlin 和 Go 都更倾向于通过接口组合行为,而非继承。
Go 示例:
type Notifier interface {
Notify(message string)
}
type EmailNotifier struct {}
func (e EmailNotifier) Notify(msg string) { fmt.Println("Email:", msg) }
type SMSNotifier struct {}
func (s SMSNotifier) Notify(msg string) { fmt.Println("SMS:", msg) }
func SendAlert(n Notifier, msg string) {
n.Notify(msg)
}
➡️ 没有继承树,只有组合和接口行为抽象。
四、组织代码的经验性比例(经验法则)
-
Go:80% 顶层函数 + 20% struct 方法
- Go 更过程化,强调清晰的 package 和函数名。
- 类(struct)仅在需要“数据+行为”绑定时使用。
-
Kotlin:60% 顶层函数 + 40% 类 / 对象
- Kotlin 可写纯函数式风格,也可写 OOP。
- Kotlin 推荐你“先写函数”,再提取类(refactor 后再抽象)。
五、实践建议
-
先函数后封装 先用顶层函数写出清晰的流程,运行通了,再看看有没有共用数据或行为需要抽象成类。
-
按功能组织,而非按类型 Java 常是:
model/ service/ controller/
Kotlin / Go 推荐:
user/ user.go service.go repo.go
也就是:按领域(domain)分包,而不是按分层(layer)分包。
-
不要过度设计 Kotlin / Go 文化都反对“空壳类”。 如果类里只有静态方法或无状态逻辑——那其实就该是顶层函数。
六、总结一句话
在 Java 中,类是容器;在 Kotlin / Go 中,函数才是核心,类只是函数的组织手段。