译文 | 为什么面向对象很糟糕

作者简介:Joe Armstrong,编程语言 Erlang 之父。
参考原文地址:Why OO Sucks by Joe Armstrong

当我第一次接触到面向对象编程的概念时,我是持怀疑态度的,可是我并不知道这是为什么——我就是觉得哪里不大对。在面向对象编程的概念面世后,它很快变得非常流行(稍后我会解释这是为什么),而对它的批评之声就像是「教堂里的誓言」(译注:形容微不足道)。面向对象的特性变成了每个受人尊敬的编程语言都不得不拥有的东西。

在 Erlang 变得越来越流行的过程中,我们经常被人问:Erlang 是面向对象的吗?好吧,尽管正确的答案是「不,当然不是」,但我们并没有当面大声的说出来。我们发明了一系列独创的方式来回答这个问题,以便给人 Erlang 是(有几分)支持面向对象但又不真的是的印象。

此刻我想起了在巴黎第七届 IEEE 逻辑编程大会上, IBM (法国)的老板在演讲中向听众强调的事情。IBM 的 prolog 已经被加入了很多面向对象的扩展,当问起为什么时他回答说:

我们的顾客想要面向对象的 prolog,所以我们创造了面向对象的 prolog。

我始终记得如此简单的回答,没有良心不安,也没有灵魂拷问,更没有问一句:面向对象真的是正确的做法吗?

为什么面向对象很糟糕

对于面向对象,我主要的反对点要落实在相关的基础概念上,我将会列出这些概念和我反对的理由。

反对点 1:数据结构和函数不应该被绑定在一起

对象在不可分割的微小单元里把函数和数据结构绑定在一起。我认为这是一个根本的错误,因为函数和数据结构完全属于不同的世界。为什么呢?

函数用来做事情,它们有输入和输出。输入和输出是数据结构,并且被函数所改动。在大部分编程语言里,函数由一系列指令组成:先做这个然后去做那个事情。为了理解函数,你必须理解事情被完成的顺序(在惰性函数式编程语言和逻辑语言里,这个限制没那么严格)。

数据结构就是数据结构,它们不做事情。它们本质上是声明式的。理解一个数据结构比理解一个函数要简单得多。

函数被视为转换输入成输出的黑匣子。如果我理解了输入和输出我就理解了这个函数,但这并不意味着我已经能够写出这个函数了。

函数通常通过观察来理解,它们是计算系统中的一部分,这个系统的工作就是把类型为 T1 的数据结构转换为类型为 T2 的数据结构。

因为函数和数据结构是完全不同的东西,所以把它们硬绑定在一起在根本上就是错误的。

反对点 2:万物皆对象

我们来考虑一下「时间」。在一门面向对象的语言里,「时间」必须是一个对象。但是在一个非面向对象的语言里,「时间」是一种数据类型的实例。例如,在 Erlang 里,有许多不同种类的时间,它们能够用类型声明来清清楚楚的定义出来,就像下面这样:

1
2
3
4
5
6
7
8
9
-deftype day() = 1..31.
-deftype month() = 1..12.
-deftype year() = int().
-deftype hour() = 1..24.
-deftype minute() = 1..60.
-deftype second() = 1..60.
-deftype abstime() = {abstime, year(), month(), day(), hour(), min(), sec()}.
-deftype hms() = {hms, hour(), min(), sec()}.
...

注意这些定义不属于任何特定的对象。它们无处不在,并且用数据结构来表示的时间能够被系统中的任何函数操作。

这里没有与之相关联的那些方法。

反对点 3:在一门面向对象的编程语言里,数据类型定义散落在各个地方

在一门面向对象的编程语言里,数据类型定义属于对象。所以我不能在某个地方找到所有的数据类型定义。在 Erlang 或者 C 里,我可以在一个简单的 include 文件或者数据字典里定义我的所有的数据类型。在面向对象语言里我不能这样做,数据类型定义散落在各个地方。

我来举个例子。假设我要定义一种无处不在的数据结构。「无处不在」指的是在系统的各个地方都有可能出现。

Lisp 程序员可能早已知道,相比于大量的数据类型和一小撮作用于它们的函数来说,定义一小部分无处不在的数据类型和大量作用于它们的函数会更好。

一种无处不在的数据结构是那种类似于链表、数组、哈希表或者时间、数据或文件名这样更高级的对象的东西。

在面向对象编程语言里,我不得不选择某个基础的对象去定义普遍存在的数据结构,所有其他想要使用这些数据结构的对象都必须继承这个基础对象。假设我现在想要创造某个「时间」对象,它应该属于哪里呢……

反对点 4:对象有私有状态

状态是万恶之源。在特定的有副作用的函数里应该被避免。

虽然编程语言中的状态可以是不可获得的,但现实世界里的状态却十分之多。我对我银行账户的状态十分感兴趣,并且当我从中存入和取出一些钱的时候,我也希望我的银行账户的状态能够被正确地更新。

提出一个问题:在现实世界里存在的状态,一门编程语言应该提供什么工具来处理它们呢?

  • 面向对象编程语言说:从程序员那里隐藏这些状态。这些状态被隐藏并且只能通过特定的访问函数来获得。
  • 传统的编程语言(比如 C,Pascal)说:状态变量的可见性由语言的作用域规则来控制。
  • 纯粹的声明式语言说:这里没有状态一说。

系统的全局状态从各个函数中进进出出。像单体结构(对于函数式编程语言)和分布式结构(对于逻辑语言)这样的机制被用来从程序员手中隐藏状态,这样他们就可以像「状态似乎不重要」一样来编程。不过,拥有对系统状态的全部访问权限是必要的。

面向对象编程语言选择「从程序员手中隐藏状态」是十分糟糕的选择。它们不但不展现状态和找到减少令人讨厌的状态的方法,反而把它们都藏了起来。

为什么面向对象如此流行?

  • 理由一:它被认为是容易学习的
  • 理由二:它被认为可以使代码更易复用
  • 理由三:是炒作
  • 理由四:它创造了一个新的软件产业

我没有明白理由一和理由二为什么成立。理由三和四似乎是技术背后的驱动力量。如果一门语言技术是如此糟糕但是它却创造了一个新的产业来解决它自己产生的问题,那对于很多想赚钱的人来说一定是个好主意。

这就是面向对象背后真正的驱动力量。

译注:文章观点仅供参考,作者本人后来也说自己的观点或许有些不成熟。