范式
No related notes
Outlinks (0)
No outlinks found
Backlinks (0)
No backlinks found
1 · 范式
S 程序本质上就是在操作状态(数据),但随着规模增长,状态管理越来越难 C 不同范式对”状态该由谁持有、能否修改、怎么传播”给出了截然不同的回答 Q 各范式如何表示状态、改变状态、控制状态?各自的取舍是什么? A 见下文
1.1 · 核心视角
从状态看编程范式:怎么表示状态、怎么改变状态、谁来控制状态
1.2 · 范式总览
- 面向过程(Procedural)—— 不隔离状态
状态存在于”变量”和”过程执行过程”里,由过程直接读写。
- 面向对象(OOP)—— 隔离状态在对象内部
状态存在于”对象内部”,通过方法(对象行为)来控制访问。
- 函数式(FP)—— 避免改变状态
尽量不原地改状态,而是返回新状态
- 响应式(Reactive)—— 状态变化自动传播
状态表示为”随时间变化的值流”(Observable/Signal),变化沿声明好的依赖图自动传播,消费者订阅而非主动轮询。
- Actor 模型 —— 状态隔离在 Actor 内部,通过消息传递
每个 Actor 拥有私有状态,外部完全不可见;Actor 之间只能通过异步消息通信,收到消息后可以修改自身状态、创建新 Actor、发送新消息。没有共享内存,从根源消除锁竞争。
1.3 · 深入对比
| 维度 | 面向过程 | OOP | FP | Reactive | Actor |
|---|---|---|---|---|---|
| 状态位置 | 全局/局部变量 | 对象字段 | 函数参数与返回值 | 流/Signal | Actor 私有 |
| 可变性 | 随意可变 | 受方法约束 | 不可变(或显式化) | 流式推导,源头可变 | Actor 内可变,外不可见 |
| 状态变化方式 | 直接赋值 | 方法调用 | 产生新值 | 事件/信号驱动 | 消息驱动 |
| 并发策略 | 锁 | 锁/监视器 | 天然安全(无共享可变) | 背压/调度器 | 天然安全(无共享内存) |
OOP 深入:
- 封装:将数据和操作绑定,对外暴露接口,隐藏内部状态表示 → 通过封装”变化”来让程序更容易理解
- 多态:允许不同对象对同一消息做出不同响应,核心价值是让调用者不必了解具体状态的形态
- 继承 vs 组合:继承共享状态结构,但容易产生”脆弱基类”问题;组合更灵活,优先使用
- 陷阱:对象之间通过引用互相持有,容易形成隐式的共享可变状态,并发下尤其危险
FP 深入:
- 纯函数:相同输入永远得到相同输出,无副作用 → 通过减少”变化”来让程序更容易理解
- 不可变数据:数据一旦创建不可修改,“修改”即创建新副本(持久化数据结构可降低拷贝开销)
- 显式化副作用:用类型系统把副作用标注出来(IO Monad、Effect 系统),让”哪里有状态变化”在类型层面可见
- 组合性:纯函数天然可组合、可测试、可缓存(memoization)
Reactive 深入:
- 核心抽象:Observable(RxJava/RxJS)或 Signal(SolidJS/Angular),将”值随时间变化”建模为一等公民
- 声明式依赖:
c = a + b,当 a 或 b 变化时 c 自动更新,无需手动通知 - 背压(Backpressure):当生产者快于消费者时,提供缓冲、丢弃、限流等策略
- 适用场景:UI 状态管理、事件流处理、实时数据管道
Actor 深入:
- 代表实现:Erlang/OTP(进程即 Actor)、Akka(JVM)
- 容错:Supervisor 树,子 Actor 崩溃后由父 Actor 决定重启策略(“let it crash”哲学)
- 位置透明:消息发送不区分本地/远程,天然适合分布式系统
- 适用场景:高并发、分布式、需要强隔离和容错的系统
1.4 · 一句话总结
面向过程 → 状态裸奔;OOP → 状态封装;FP → 状态不变;Reactive → 状态流动;Actor → 状态隔离+消息;
没有最好的范式,只有最适合问题域的范式。现代语言(Rust、Scala、Kotlin)往往混合多种范式。