从面向对象到事件化:一种编程范式的思考
Updated:
Contents
从面向对象到事件化:一种编程范式的思考
前言
这篇文章记录了一次关于编程范式的思考,从公开/私有方法的设计,到面向对象与面向过程的关系,再到事件化模型如何解决并发问题。
核心观点:没有对立,只有平衡;没有非此即彼,只有恰到好处。
一、公开与私有:不是对立,是平衡
为什么有私有方法?
封装(Encapsulation)的目的:
- 隐藏实现细节
- 减少耦合
- 简化使用
极端的代价
| 极端 | 问题 |
|---|---|
| 全公开 | 改不动,到处是依赖 |
| 全私有 | 没法用,没法测 |
结论
如果一个私有方法复杂到需要单独测试,它就不应该是私有的。
私有方法的本意是”简单的实现细节”。复杂逻辑应该提取成独立的可测试单元。
二、面向对象与面向过程:不是矛盾,是互补
静态方法的本质
// Java 的静态方法
Math.abs(-1)
// Go 的包级函数
math.Abs(-1)
静态方法 = 披着 OOP 外衣的面向过程。
Java/C# 要求所有代码必须在类里,但有些逻辑不需要对象状态(如 Math.abs()),所以用 static 妥协。
范式选择
| 场景 | 适合范式 |
|---|---|
| 管理状态 + 行为 | 面向对象 |
| 纯计算、无状态 | 面向过程 / 函数式 |
| 数据变换 | 函数式 |
好代码 = 混合使用,按需选择。
三、函数式编程:定义规则,而非执行步骤
命令式 vs 声明式
命令式(过程式/OOP):怎么做
1. 拿个变量
2. 循环
3. 累加
4. 返回
函数式:是什么
factorial = n → n * factorial(n-1)
就是数学定义,映射关系
例子
| 风格 | 求和 |
|---|---|
| 命令式 | sum=0; for(i:list) sum+=i; return sum; |
| 函数式 | sum = reduce(+, list) |
本质
函数式 ≈ 定义规则 / 数学映射
面向过程 ≈ 描述步骤 / 执行流程
四、Go 对 OOP 的简化
传统 OOP 的问题:
- 继承层次太深
- 设计模式满天飞
- 过度抽象
Go 的做法:
| 传统 OOP | Go |
|---|---|
class Dog extends Animal |
type Dog struct { Animal } |
class Dog implements Pet |
有方法就算实现(鸭子类型) |
| 继承链 | 扁平组合 |
组合优于继承。
五、事件化模型:替代递归/栈
传统模型的问题
调用 → 入栈 → 返回 → 出栈
↓
共享状态
↓
锁、竞态、死锁
事件化模型
函数 A:
处理 → 发出事件 E1
↓
函数 B 收到 E1:
处理 → 发出事件 E2
↓
函数 C 收到 E2:
处理 → 不再发出事件
↓
生命周期结束
优势
- 无共享状态
- 天然异步,不需要 async/await
- 无锁、无竞态
已有实现
| 技术 | 体现 |
|---|---|
| Erlang/Elixir | Actor + 消息传递 |
| Go | goroutine + channel |
| JavaScript | 事件循环 |
六、生命周期管理:holder_id 模式
传统方案的问题
- 消息复制有性能开销
- 引用传递有生命周期追踪问题
解决方案
事件 {
data,
holder_id // 当前持有者(队列或处理者)
}
holder_id == null → 可回收
像接力棒:
- 棒上写着当前持有人
- 传出去就不再是你的
- 没人拿就可以扔掉
七、无竞争分配:预订模式
传统方式
事件产生 → 多人抢 → 锁
事件化方式
生产者生成事件时:
事件 {
data,
target_id: 处理者3, // 生成时就定了
}
↓
直接发到处理者3的信箱
↓
根本不存在竞争
类比
传统:到酒店门口排队抢房
事件化:提前预订,到了直接入住
声明关系,而不是执行抢夺。
八、分布式时序:向量时钟 + 柔性事务
核心思想
不追求"绝对时间一致"
↓
只追求"相对顺序可推算"
↓
每个节点记录:我比谁快/慢多少
↓
读写时本地计算全局顺序
柔性事务
- 不追求强一致性
- 允许中间状态不一致
- 最终结果一致
- 对时间隔按业务动态调整
| 业务场景 | 对时间隔 | 一致性 |
|---|---|---|
| 金融交易 | 短(毫秒级) | 强 |
| 社交点赞 | 长(秒/分钟级) | 弱 |
| 日志采集 | 很长 | 最终 |
九、总结
统一模式
看似对立的概念
↓
其实是需要平衡的两端
↓
好设计 = 按需在中间取点
↓
极端 = 问题
设计原则
| 原则 | 说明 |
|---|---|
| 无共享 | 状态隔离,消息传递 |
| 关系前置 | 生成时绑定,而非运行时竞争 |
| 声明式 | 定义规则,而非执行步骤 |
| 动态权衡 | 一致性/性能按业务调整 |
核心收益
| 问题 | 解决方式 |
|---|---|
| 并发竞态 | 无共享状态,只有消息 |
| 生命周期 | holder_id 轻量追踪 |
| 死锁 | 无锁,只有消息队列 |
| async/await | 不需要,天然异步 |
| 分布式事务 | 柔性事务 + 动态对时 |
后记
编程范式不是信仰,是工具。
面向过程解决「怎么算」
面向对象解决「怎么组织」
函数式解决「怎么声明」
事件化解决「怎么并发」
没有银弹,只有 trade-off。场景决定方案,不是方案决定场景。
参考
- Erlang/OTP: Actor 模型
- Go: goroutine + channel
- Rust: Ownership + Borrowing
- Lamport Clock / Vector Clock
- CAP 定理与 BASE 理论
