软件设计

面向对象的本质是消息:Alan Kay 所说的真正 OOP

学习面向对象时,我们总是先背下继承、封装、多态这三个词。

4 分钟阅读
面向对象的本质是消息:Alan Kay 所说的真正 OOP 封面图

面向对象的本质是消息:Alan Kay 所说的真正 OOP

学习面向对象时,我们总是先背下继承、封装、多态这三个词。

然而,创造“面向对象编程”这一术语的 Alan Kay,并没有把这三者视为核心。

先说重点:Alan Kay 所说的真正 OOP,本质是对象之间传递的消息,而不是对象本身。比起如何划分类,更重要的是对象彼此交换什么。

读完全文,你会明白他为什么甚至说自己“后悔使用面向对象这个名称”,以及这一视角如何改变我们的代码。


Alan Kay 为什么后悔使用“OOP”这个名称?

Alan Kay 在 20 世纪 70 年代于 Xerox PARC 创建了 Smalltalk。“面向对象编程”这一术语也出自他之口。

2003 年,一位开发者通过电子邮件问他“什么是 OOP”,他这样回答:

“我后悔使用了‘对象’这个词。

因为这让人们把注意力转向了不那么重要的概念。真正伟大的想法是‘消息传递’。”

第一次看到这句话时,难免有些困惑。我们学到的面向对象一直都是“如何设计优秀的对象”。

在 Kay 看来,真正重要的不是对象内部,而是对象通过传递消息形成的关系与交互,这才是系统的本质。

他把程序比作生物细胞。每个细胞都严密隐藏内部,只通过化学信号,也就是消息进行通信。互联网也一样:无数计算机各自独立运行,只交换消息。


以消息为中心的思维有什么不同?

“调用方法”和“发送消息”看起来相似,但视角不同。

方法调用更接近“执行这个对象的这个函数”。发送方需要在一定程度上了解接收方的内部。

而消息更接近“请处理这个,方法由你决定”。发送方只需要结果,不必知道对方如何处理。

来看一个例子。下面的代码通过“消息”把折扣计算委托给各个会员等级对象。

protocol Member {
    func discountedPrice(for price: Int) -> Int
}

struct Gold: Member {
    func discountedPrice(for price: Int) -> Int { price * 80 / 100 }
}
struct Silver: Member {
    func discountedPrice(for price: Int) -> Int { price * 90 / 100 }
}

// 发送方完全不知道各等级的计算方式.
let members: [Member] = [Gold(), Silver()]
for m in members {
    print(m.discountedPrice(for: 10000))
}
// 输出: 8000
// 输出: 9000

调用方代码中既没有 if,也没有等级名称。

它只发送“请计算折扣价”这一消息,实际计算由各个对象负责。即使新增等级,也无需修改调用方。

这就是 Kay 所说的消息传递的力量。重点不是把对象拆得很细,而是采用降低耦合的沟通方式。

调用方无需知道等级,只要发送消息即可
调用方无需知道等级,只要发送消息即可

那么封装和多态就不需要了吗?

不,恰恰相反。

认真理解消息传递后,封装和多态会自然出现。

对象若只能通过消息通信,就必须隐藏内部状态。这就是封装。同一条消息由不同对象做出不同响应,就是多态。

这些概念不是需要死记的规则,而是以消息为中心设计时自然产生的结果。

问题在于顺序。很多人从“先把类划分好”开始。这样虽然会产生大量对象,却也容易让它们彼此看透内部,最终得到只有名字像面向对象的代码。

Kay 的视角颠倒了顺序。先问:“这个对象应该响应哪些消息?”

我在画图之前先写下了这个问题
我在画图之前先写下了这个问题

什么时候使用这个视角,什么时候应该适当收敛?

以消息为中心的设计并不总是正确答案,重要的是根据情况使用。

情况 判断
需求经常变化的领域逻辑 采用以消息为中心的委托结构更有利
协作对象很多的复杂流程 对降低耦合度非常有效
简单的数据转换或计算脚本 不要勉强封装成对象,使用函数即可
性能极其重要的部分 过度抽象反而会成为负担

总结如下。

  • 越是协作和变更多的地方,消息视角越能发挥作用。
  • 对于简单且固定的逻辑,保持轻量更好。
  • 请记住,目标不是“划分对象”,而是“设计沟通方式”。

面试时可以这样回答

Q. Alan Kay 所说的面向对象本质是什么?

不是对象本身,而是对象之间传递的消息。Kay 将对象比作能够独立通信的细胞,认为消息传递比继承和封装更具普适意义。

Q. 以消息为中心的设计在实际工作中有什么好处?

调用方无需了解对方的内部实现,因此耦合度会降低。这样即使新增类型,也无需修改调用方,结构更能适应变化。


如果觉得面向对象很难,请在画类图之前先想想:“这些对象彼此在说什么?”

只改变一个视角,就能感受到代码变得更加稳固。祝你今天也能做好设计!

延伸阅读