混合OO和Functional设计

面向对象定位于系统高层次,面向函数编程是定位于低层次. 来自
Tell Above, and Ask Below - Hybridizing OO and Functional Design重新定义了面向对象,纠正了以往对OO的错误观点:

Object-orientation is better for the higher levels of a system, and functional programming is better for the lower levels. 面向对象定位于系统高层次(更靠近人),面向函数编程是定位于低层次(更靠近机器).

文章首先各自重申定义了面向函数FP和面向对象OO的定义:

(1)函数式编程是为了减少副作用,当没有副作用时,你就能透明引用,可以将表达式从一个地方拷贝到另外一个地方,只要给予同样输入,无论如何调用,总是给出同样的输出。这称为purity纯度。

纯度能够激活懒加载,在面向函数语言中,calling a function调用一个函数和apply a function应用一个函数意义是完全不一样的。

(2)关于面向对象,作者引用了最初原始的对象定义:Alan Kay认为对象是道法自然处理复杂系统的方式,使用对象方式来处理复杂软件系统,在生物学中,一个有机体中有很多神经元Cell,这些神经元之间通过化学消息进行彼此联系,这点非常类似Smalltalk 使用消息发送message send而不是功能调用functional call,两者不只是巧合。

对象结构的好处是它更显式强调Play,而不是Player,正如Alan Kay认为:消息比对象本身更加重要(banq注:JdonFramework基于事件消息的特性反映了OO本质),这在生物学也同理,你不可能通过杀死一个Cell来搞垮整个有机体,这就证明了越接近Erlang的处理模型就越是典型的面向对象系统。

[该贴被admin于2012-04-23 12:09修改过]
[该贴被admin于2012-04-23 12:10修改过]

早在2000年, Dave Thomas 和 Andy Hunt写了一段设计指导,称为‘Tell, Don’t Ask.’(只是告诉交代,别索要),当你吩咐对象应该为你做什么即可,而不是让他们带着他们数据,由你来处理这些数据,这样才会带来完美的封装。(banq注:因为对象只要您吩咐一身,就自己内部搞定了。)

在生物学中,神经元之间化学消息是异步的,一个神经元发送消息并不会堵塞直至接受到消息,但是,目前很多典型的面向对象系统是这样的(通常的方法调用就是这样,调用者堵塞直至被调用者的方法处理完毕),当我们调用另外一个对象的方法,必须在那里等待直至那个对象的方法返回一个结果给我们,其实我们还有其他事情要做,对象很忙,这种同步方式调用实际侵犯了 'Tell, Don't Ask'原则, 因为,等待返回结果是典型的‘ask’(索要结果)。

如果我们认为面向对象OO类似生物神经元,那么目前我们大多数所谓OO技术是错误的. 在这些编程语言如Java .NET中有类和对象,但是一旦我们实现同步方法调用,这些概念就不再是他们名字那样了,名非名了。

那么有比这些所谓OO更加真正OO吗?是的,IT架构中的消息系统。

前面文章提到:消息系统才是真正的OO。下面以代码说明:


Class A{
void m(){
}
}
Class B{
void m1(){
...
a.m(); //同步堵塞方法
...
}
}

代码中,B对象中a.m()就是上文提到的同步堵塞方法,B在这里Ask索要A的m方法结果,自己的事情干不了,这是不符合类似神经元的工作原理,如果真是这样,可能是神经病的表现了。

那么,异步方法如何实现呢?Erlang或Scala的trait都能够优雅实现,而在Java中,可以使用基于Disruptor的JdonFramework实现,具体可见:
非堵塞并发编程

以上B代码使用Jdon框架改写大概如下:

@Send("事件名称")
a.m();

通过元注解即可完成异步方法调用。

真正OO应该是异步消息交互的,这点在UML的顺序图中也是如此,如下图:



[该贴被banq于2012-04-23 13:16修改过]


综上所述,我们在看面向对象设计和函数编程时,他们其实是不矛盾,有并行存在的部分。

OO最适合Tell吩咐,当你吩咐时,对象封装切分其实最大化解耦实体之间的耦合,为了进一步防止耦合,你必须让你的消息异步发送。这些都是通过 tell 模型做到的.

函数编程FP最适合ask索取. 其实在纯函数编程中,如果一个函数方法不返回任何结果几乎是毫无意义的,除非是为了考虑副作用(边际影响),函数化Functional纯洁性purity将会激活懒加载,这非常类似OO中激活异步一样。(banq注:在JdonFramnework中,可以用事件实现消息发送,也可以实现懒加载,特别是数据库数据懒加载。)

好了,如果我们接受这些预设,那么我们如何组织我们的系统呢?

假设顶层有一个函数式层,当里面的表达式被演算后,执行消息的发送,但是这会造成系统理解上的问题,也会产生副作用,破坏函数的纯洁性。

那么有没有其他方向呢? 如果我们将对象层放在顶部,允许对象使用底部的函数片段?这好像真的没有什么问题。无副作用的函数适合内部机制,面向对象非常适合高层,能够解耦信息系统是最重要的。

现在有一个疑问,象Scala这样允许程序在任何抽象层次混合对象和函数,甚至你能够使用函数进行查询过滤对象,微软的LINQ技术也是在对象层面上建立函数层面,这些好像违背上面的预设。

无论如何,我们已经知道,真正的面向对象在现代软件架构中是存在服务或消息层面,那么在这样架构下,非常适合“tell above , ask below.”在上面吩咐,在下面索要。

全文译完。原文中也有不少讨论。
[该贴被admin于2012-04-23 13:50修改过]

对OO,似乎有很多解释。
比如有的说,OO其实就是归类。有的说OO并不一定是对现实世界的模拟。所以说,神经元这东西,不是很具有代表性吧。。。

2012-04-24 12:32 "@lostalien"的内容
OO其实就是归类。有的说OO并不一定是对现实世界的模拟。所以说,神经元这东西,不是很具有代表性吧 ...

原文没有否定OO是归类,归类能够解耦实体之间关系,但这只是分析设计时的OO,那么类一旦变成对象在内存里运行了,如何相互调用,这点是本文着眼点,其实UML顺序图已经指定了使用消息调用,但是当初因为线程机制问题,而且是单核CPU,这些底层限制了消息调用的实现,现在有了Erlang或Scala的Actor,在Java中我们使用Disruptor也能实现消息事件的并发调用(如JdonFramework等),实现Tell模型。

如果未来Java 8中再增加函数语言的一些特性,也许Java这种先“对象”后“函数”,与Scala的“类”与“对象”同等重要有定位的差异。Scala最大的一个潜在问题可能就来自于这里,它的特点是first class是functional,导致先"函数"后"对象",这样语言做出复杂系统,可能理解性易读性上没有first class是对象的系统好理解。所以,有人提出疑问:Is Scala Only for Computer Scientists Scala仅仅是和计算机科学家吗?

其实我们国家的正规院校软件教育也是一种Computer Scientists计算机科学家教育模式。



[该贴被banq于2012-04-24 13:21修改过]

自从有了OO以后,对OO的争论就没停止过。
世间万物是由不同的实体构成的,而OO可以更好的来模拟这种客观存在,从系统分析和设计的角度,OO更容易能让人理解,接着到了实现阶段,其实OO和FP是可以结合的,OO贯串了分析,设计和实现的全过程,而在实现的阶段,FP可以纳入进来对OO进行补充,它们两者并不矛盾。

2012-04-25 22:08 "@xmuzyu"的内容
自从有了OO以后,对OO的争论就没停止过 ...

可能还取决于我们对世界的理解:过程还是结果,是函数还是实体。

原文作者也是从实体对象这个角度切入,不能避免有先入为主的嫌疑,在另外一篇文章:罗素悖论 类型系统与编程语言中提到,函数学派认为一切都是函数,世界用函数切分也很好理解,也很直观。

真是让我们太纠结。

函数和对象就像病毒和细胞
都有自己得活性 其特性取决于环境
没有什么完美 恰当时候两者可以共存达到平衡.

无论什么样的系统最终必然落实到各个函数实现上,这样的实现必然依托于过程。

在学校讲述这些过程的时候,忘记了系统层面,所以绝大多数人没有OO的思想方式。

在他们后来接触OO的时候又忽视了代码是怎么实现的,似这样的想法又会发现OO的问题而大肆宣扬。

究其根本是没有学会思考,没有学会思考的层面和学习的方法,除了别人说的自己都不会。不能不说是教育制度的绝对的过失。

2012-04-24 13:20 "@banq"的内容
如果未来Java 8中再增加函数语言的一些特性,也许Java这种先“对象”后“函数”,与Scala的“类”与“对象”同等重要有定位的差异。Scala最大的一个潜在问题可能就来自于这里,它的特点是first class是functional, ...

scala中函数定义与类定义,函数体与对象体是相通的,我并不感到谁先谁后的问题。

每个类(或对象)都有一个apply方法,可以覆盖该方法来使得对象获得函数意义。而反过来每个函数都可以理解为Function类的实例。

而那篇外文,我觉得是针对可变性的思考。是对iterator只能一次遍历的疑惑。我觉得是作者对iterator的误解。

没有对象的日子是一篇完全否定Object对象的文章:

文章认为:对象导致下面情况:
1.业务组件无法重用。

2.模式并不能建立一个良好结构的软件系统:模式导致复杂难以维护和拓展,一些模式甚至导向反模式,例如单例模式几乎难于单元测试。必须明智地使用模式。

3.框架或组件只有很少优点的重用。EJB之类框架能够隐藏复杂技术,作者认为集合Collection比类还要更加抽象隐藏呢。(banq注,这个观点有些极端,类框架可以看成是一种行业性质的集合,集合的细分)

4.继承导致软件脆弱,但是如果让领域模型实现各种多样接口,也导致复杂性,提倡Mixin。

5.违背封装本义,面向对象关键是将状态和改变状态的行为封装起来,实际上我们更多关注状态值,而非行为,对于一些复杂行为就难以封装,比如HTML格式的转换,这种情况不如将对象传入一个转换方法,然后读取对象中的数据,现在一些反模式如DTO或JavaBean提倡的贫血模型,只封装数据,没有行为,如果这样,为什么不直接操作数据结构,需要对象封装干嘛呢?(banq注:使用类或对象进行数据结构封装,实际是一种归类贴标签的良好组织方式,类是一种包装盒,主要便于识别)

6.可变状态导致痛苦。作者提倡不可变状态。

该文引起的讨论很多,banq认为实际很多还是由于Java .NET等并不是一种真正OO语言导致的,真正面向对象方式正如本文开头一篇文章提到,对象之间的交互应该是以异步消息,而不是同步僵化的行为。

总体来说:传统面向对象语言对于行为的表达不够丰富,没有Scala等函数语言动态立体。