multi-paradigm programing of ChatGPT

困扰:

我原是 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 后再抽象)。

五、实践建议

  1. 先函数后封装 先用顶层函数写出清晰的流程,运行通了,再看看有没有共用数据或行为需要抽象成类。

  2. 按功能组织,而非按类型 Java 常是:

    model/
    service/
    controller/
    

    Kotlin / Go 推荐:

    user/
        user.go
        service.go
        repo.go
    

    也就是:按领域(domain)分包,而不是按分层(layer)分包。

  3. 不要过度设计 Kotlin / Go 文化都反对“空壳类”。 如果类里只有静态方法或无状态逻辑——那其实就该是顶层函数。


六、总结一句话

在 Java 中,类是容器;在 Kotlin / Go 中,函数才是核心,类只是函数的组织手段。