重看面向对象
软件的本质是什么?从不同的角度来看,会有不同的答案。有人认为是程序加文档,有人认为是人机交互,有人认为是增删改查,有人认为是抽象模型,而我认为是算法。
计算机科学的基石是图灵机抽象:一个输入集合,一个输出集合,一个内部状态集合,一个计算规则集合。这个抽象十分强大,我们甚至可以认为一头奶牛也是一个图灵机:吃的是草,挤的是奶。
一个表达式也是一个图灵机,其中的操作数是输入,求值的结果是输出。一个函数也是图灵机,参数是输出,返回值是输出。编程或设计软件就是在通用图灵机的基础上,设计一个具体的图灵机。我们设计软件接受怎样的输入,设计软件内部的状态,设计表示计算规则的代码,设计软件的输出。
近年来逐渐流行的函数式编程,就是建立在这个抽象的基础上。而且函数式编程的思想由来已久,可以追溯到最古老的高级语言之一:Lisp。这种思想非常简单:给定一个输入集合,经过函数的处理,给出一个输出集合。由此也导出了Map-Reduce等流行的架构设计模式:一个计算集群仍然是一个图灵机。
纯粹的函数有一点不足,它没有内部状态。可以说,它是简化了的图灵机。但在有些时候,我们确实需要内部状态。根据内部状态的不同,对于同样的输入,可能给出不一样的输出。于是便有了闭包的概念,它是一个函数加上相关的上下文环境状态。这样,我们可以毫无困难地构建任何具体的图灵机(好吧,正确实现一个算法还是比较困难的)。
闭包可以看成是拥有内部状态的函数,这就相当于一个简单的对象,它只有一个方法。反过来,对象可以看成是几个闭包,它们共享了内部状态。所以有人说:闭包是懒人的对象,对象是懒人的闭包。因此,函数式编程和面向对象思想,在底层基础上是一致的。
面向对象思想的历史和函数式一样久远。实际上,它们都是我们在设计算法时的一种抽象。只有利用抽象概念,才能实现人与人之间的沟通。“你想吃苹果吗?”这里的“苹果”就是一个抽象概念,它隐藏了苹果实现的许多细节。人的大脑喜欢工作在一组抽象概念上。名词是结构或存在的抽象,动词是行为或过程的抽象。
我们在设计算法时,既需要函数抽象,也需要对象抽象。今天,面向对象和函数式编程的思想在各种编程语言中融合,可以说是殊途同归。
抽象是强大的工具,但用得不好,也会产生不良的后果。最重要的问题,就是创建太多不必要的抽象。毕竟,抽象只是我们脑中的概念,我们可以创造出任何概念。比如上帝和各种鬼神,直到科学家说,在科学的系统里不需要假设存在一个上帝。面向对象在这方面遇到的问题比较多。举例来说,一个Java的Hello world程序,就要涉及好几个概念,直接导致程序的代码比较长。而在函数式编程中,这通常只是一次函数调用。又比如,在一个使用Struts、Spring、Hibernate构建的Java Web应用程序中,处理一个Get请求的调用栈,可能是长长的一串。数据在不同的概念抽象之间反复倒手,白白浪费了计算资源。
任何两种观点都是互补的。面向对象思想在过去的软件开发中取得了辉煌的成绩。函数式编程让我们能从另一个角度审视面向对象,更进一步体会面向对象抽象的强大,也发现面向对象中一些误用的地方。如无必要,勿增实体。也许我们不需要假设以太的存在,就能解释光在真空中的传播。
多年后重读这本书,促使我重新思考,需要利用哪些抽象来设计我的算法。
这些年来,这本书让我受益良多,再次向大家郑重推荐。
王海鹏
2016年1月5日