面向对象不是 class:一次对 OOP、ADT 与现代 C++ 的重新认识

enter image description here

C++经典阅读笔记- Advanced C++ Programming Styles and Idioms

面向对象不是 class:一次对 OOP、ADT 与现代 C++ 的重新认识

很多人以为: 面向对象 = class + 继承 + virtual

但当你写得越久、系统越复杂,就越容易发现: 问题往往不是“不会写类”,而是“抽象从一开始就错了”。

这篇文章想讨论一个常被忽略、却极其关键的问题:

面向对象,到底是一种语言特性,还是一种设计思想?


一、一个常见但危险的误解

在绝大多数教学和实践中,面向对象往往被这样介绍:

  • class
  • private / public
  • 有继承
  • 有多态

久而久之,很多人形成了一个潜意识等式

“我用了 class,我就在做面向对象设计。”

但事实是:

你完全可以写满 class,却完全没有任何好的面向对象设计。


二、面向对象真正的起点:抽象数据类型(ADT)

在语言出现 class 之前,面向对象的核心思想就已经存在

它的名字叫:

抽象数据类型(Abstract Data Type, ADT)

ADT 的核心只有三点:

  1. 数据与操作绑定
  2. 隐藏实现细节
  3. 通过明确的接口约束使用方式

注意: 这里没有任何语言关键词。


三、为什么 C 语言也可以写“正统的 OOP”?

看一个非常经典的 C 语言例子:

// stack.h
typedef struct Stack Stack;

Stack* stack_create(void);
void   stack_push(Stack*, int);
int    stack_pop(Stack*);
void   stack_destroy(Stack*);
// stack.c
struct Stack {
    int data[100];
    int top;
};

这个设计已经满足:

  • 用户无法访问内部结构
  • 所有操作通过接口完成
  • 实现细节完全隐藏

这在设计层面已经是完整的面向对象抽象。

区别只是:

  • 在 C 中靠自律
  • 在 C++ 中由编译器强制

四、C++ 到底改变了什么?

一个关键结论是:

C++ 并没有发明面向对象,它只是让抽象“更安全、更低成本”。

C++ 提供的是一整套实现 ADT 的语言级工具

C++ 特性 实际作用
class / private 编译期强制封装
构造 / 析构 生命周期自动管理
RAII 把资源管理变成类型属性
模板 抽象前移到编译期
Concepts 显式表达接口契约

但请注意:

这些都是“工具”,不是“设计原则”。


五、现代 C++ 的一个重要转向:抽象前移

现代 C++(C++17/20/23)正在做一件非常一致的事情:

  • 减少继承
  • 减少运行时多态
  • 增强编译期抽象
  • 强调值语义(value semantics)

例如,用 templates + concepts 描述抽象:

template<typename T>
concept Shape = requires(const T& s) {
    { s.area() } -> std::convertible_to<double>;
};

这本质上是:

用类型系统描述 ADT 的“行为契约”,而不是构造继承层次。

这正好呼应了早期 OOP 理论中的一个观点:

类型系统主要存在于编译期,而不是运行期。


六、为什么“class 是 OOP 核心”的观念如此顽固?

原因并不复杂:

  1. class 是可见的语法
  2. 抽象边界是不可见的设计决策
  3. 教学往往从语法讲起,而非从设计讲起

于是我们学会了:

  • “怎么写类”
  • 却很少被教:

    • “什么时候不该写类”
    • “抽象边界应该画在哪里”

七、一个更准确的三层模型

你可以这样重新理解三者的关系:

  • 面向对象:设计范式(语言无关)
  • ADT:面向对象的工程基础
  • C++:提供多种机制实现 ADT 的系统级语言

因此:

  • C 可以写优秀的面向对象系统
  • C++ 也可以写灾难级的“伪 OOP”

八、一个简单但有力的判断标准

下次你评估一个“面向对象设计”时,不妨问自己三个问题:

  1. 抽象是否独立于实现?
  2. 接口是否稳定,变化是否被隔离?
  3. 类型是否在帮助你避免错误,而不是制造复杂性?

如果答案是否定的:

那么不论用了多少 class,它都不是好的 OOP。


九、结语

面向对象不是“我用了什么语法”, 而是“我在哪里划清了抽象边界”。

当你意识到这一点时:

  • class 会变成可选项
  • 继承会变成罕用工具
  • 抽象,会重新回到设计的中心

这,才是现代 C++ 与早期 OOP 理论真正的交汇点。


有了上面的思考,应该让我们可以处于非常正确的认知节点上了。

评论