在软件工程的演化史上,我们似乎经历了一个巨大的钟摆运动。
从 C 语言的自由狂放,到 Java/C++ 面向对象(OOP)的森严壁垒,再到如今 Rust 和 Go 的崛起。很多人认为这是一种“倒退”,因为它丢弃了继承,丢弃了复杂的类。
但实际上,这是一种螺旋上升的综合(Synthesis)。
现代编程范式并非简单的“去 OOP 化”,而是站在 C 与 OOP 的中间,利用强大的类型系统作为粘合剂,达成了一种更高级的哲学境界:该聚合的聚合,该分离的分离。
第一阶段:C 语言的“完全分离” (The Thesis)
哲学:数据就是数据,代码就是代码。
在 C 语言的时代,世界是平铺直叙的。
- 分离:
struct定义内存布局,函数定义逻辑。两者在语法上没有必然联系。 - 自由:你可以写一个函数去操作任何符合内存布局的数据。
- 代价:这种分离过于彻底,导致了“内聚性”的缺失。数据缺乏保护,逻辑散落四处,全靠程序员的自律来维持秩序。
第二阶段:OOP 的“强行聚合” (The Antithesis)
哲学:世界是由对象组成的,数据必须和行为绑定。
为了解决 C 的混乱,OOP 走向了另一个极端。
- 聚合:OOP 认为数据(属性)和行为(方法)必须死锁在一个
Class容器里。 - 约束:通过继承(Inheritance)来复用代码,通过
private来保护数据。 - 代价:这种聚合过于僵硬。为了复用一个简单的功能,你不得不继承庞大的父类(“香蕉与大猩猩问题”)。业务逻辑被层层叠叠的类结构锁死,修改变得极其困难。
第三阶段:现代范式 —— 中庸之道 (The Synthesis)
Rust、Go 等现代语言的出现,标志着我们终于在“分离”与“聚合”之间找到了平衡点。它们的设计哲学可以概括为:物理上分离,逻辑上聚合,类型上约束。
1. 该聚合的聚合:回归 Struct 与 模块化
我们不再需要庞大的 Class,但我们依然需要高内聚(High Cohesion)。
- 数据聚合:我们沿用了 C 语言的
struct,因为它在内存中是最紧凑、最高效的表达方式。 - 封装聚合:我们没有退回到 C 的“裸奔”。通过现代语言的模块系统(Module System)(如 Rust 的
pub(crate),Go 的首字母大小写),我们将相关的数据和逻辑在模块/包的层面上聚合。 - 以前是“类封装”,现在是“模块封装”。范围更宽,更灵活。
2. 该分离的分离:Trait 与 Interface
我们承认,**行为(Behavior)**往往是跨越数据类型的。
- 行为分离:不管是
Duck还是Car,只要能Run,就是可行的。现代语言将行为剥离为Trait(特质)或Interface(接口)。 - 去耦合:逻辑不再硬编码在继承树里。行为变成了可以随意插拔的“插件”。
3. 关键变量:强大的类型系统 (The Type System)
这是区分“现代语言”与“C 语言”的核心区别。如果说 C 靠自律,OOP 靠类层级,那么现代语言靠的是编译器与类型系统。
它在“分离”与“聚合”之间架起了一座安全的桥梁:
- 代数数据类型 (ADT) / 枚举 (Enum): Rust 的 Enum 比 OOP 的多态更精准。它允许你定义“要么是 A,要么是 B”的数据结构(Sum Type),配合 模式匹配(Pattern Matching),你可以在不使用继承的情况下,优雅地处理多种状态。
- 泛型与约束 (Generics with Bounds):
fn process<T: Speakable>(item: T)这句话翻译过来就是:“我不关心item是什么数据结构(分离),但我要求它必须具备说话的能力(聚合约束)。”
案例对比:思维的跃迁
让我们看一个简单的例子:处理 HTTP 请求。
- C 语言思路:定义一个
struct Request,写一堆独立的函数parse_request,handle_request。如果处理错了类型,运行时崩溃。 - OOP 思路:定义
AbstractRequest基类,派生GetRequest,PostRequest。逻辑分散在继承树里,难以直观看到全貌。 - 现代思路 (Rust):
- 聚合数据:用
struct定义请求体。 - 类型约束:用
enum Method { GET, POST }穷举所有可能性(编译器保证你不会漏掉任何一种情况)。 - 分离行为:定义
Handlertrait。 - 组合:通过
impl Handler for Request将它们联系起来。
结语
现代编程范式之所以迷人,是因为它不再执着于“万物皆对象”的教条,也不再容忍“指针满天飞”的混乱。
它汲取了 C 语言Struct的高效与直观,吸收了 OOP 封装的思想,最后引入了数学般严谨的类型系统。
这就是**“该聚合的聚合,该分离的分离”**:
- 让数据在内存中保持紧凑(聚合)。
- 让行为在逻辑上保持解耦(分离)。
- 让编译器在构建时保驾护航(类型系统)。
这也许才是软件工程成熟的标志。