要讨论走没走弯路,先要知道:
一切皆对象的口号显然有点过了,但也不至于说走了多少弯路。
这个问题跟「子类型(subtyping)是不是错误(ill-defined)的东西?」有点类似,就是程序员突然发现工具箱里面有些工具没了的话好像也没关系,就开始怀疑它们是不是多余的或错误的设计。OOP是个模糊的概念,它不仅仅指程序语言的那些特性,OOP之前必须先OOD (Object Oriented Design),不可能脱离OOD谈OOP,如果只谈概念,我们来看看:
如何才能划出一条清晰的边线界定OOP?对象内部状态?这可就麻烦大了,所有非纯函数式编程都不可能完全避开内部状态。如果你就是要原教旨 Pure FP 大法好?那……你开心就好:
type MonadStackHell a b c = AppT (MaybeT (ReaderT a (StateT b IO))) c
不要跟我提Extensible effects,既然Haskell都只能做到这种地步,为什么它就不算走弯路?
有些人不爽的地方可能在于,数据和行为是两个不同的维度,而现有的OOP语言都把数据和行为绑定到了一起,不得不用 Extensible Visitor Pattern 绕弯。可这本身就是难解之题,Haskell 里面虽然可以很方便地用 Typeclass 扩展方法,但添加新的数据却成了问题。Data types à la carte 那种方式虽然可以无限扩展数据,但却增加了转型的运行时开销,鱼和熊掌不可兼得,OOP 语言选择了另一条路,但子类型虚函数又会增加方法调用的运行时开销。(我也不提 Object Algebras
只从编程语言角度思考就进入了死胡同,我们要从OOD的角度看。在设计时自然说 A student is a Person,继承了Person的属性和方法,Haskell不支持Record继承,难道在设计时也说Student包含一个Person?然后白板上 Functor、MonadTrans、Constraints 满天飞?这样看将 Subtying 和 Inheritance 捆绑在一起还真是非常贴心,要是只说Student是Person的子类还不够,还要加一句Person有的ABCD接口Student都继承实现,那就太啰嗦了 。对象思维方式显然更易于描述现实中的大部分业务模型。它有它的局限性,但离泛滥还远着呢,我看到更多的是很多程序员根本不懂面向对象设计,甚至根本就没有 Design 这一步,从这方面来讲,不但不算是弯路,甚至还应该继续大力推进。问题的关键并不在于OOP,而在于错误的OOD。
如果OOD本身就是错的,那么正确的又是什么?我觉得现在我们还不能完美回答这个问题,有一个词呼之欲出: Language Oriented Design,但是我们还有很多路要走,而且我很怀疑有些业务逻辑天生更适合用OOD描述,换成LOD只不过是重新发明一个OO DSL而已。
Language-oriented software engineering,a book review of Clean Architecture
我觉得把面向对象作为一个整体来讨论是有问题的,我们不妨拆开来讨论各个子议题,封装是不是编程语言发展中的弯路?面向接口是不是编程语言发展中的弯路?数据和行为捆绑是不是编程语言发展中的弯路?
当把问题拆开后我们发现其实单单拎出其中一项都有各自的意义,至少谈不上弯路。那么OOP不过是把这些东西用某种方式整合在一起了而已。