[Updated: Feb 18, 2007, 增加了关于Java中静态方法的内容]
首先,这里的OO中的Object仅指包含可变状态的Object,暂不涉及有关OO的多态、继承等概念。
一、Erlang的OO
1、保存在函数调用栈中的状态
Erlang是函数式语言,一般而言,对于事物可变的状态(参数和中间状态)尽可能局限在函数调用中处理完,在调用过程中这些状态全部保存在函数的调用堆栈中,也就是说,函数处理的是事物瞬间的状态。这时,可以说,Erlang不太关心包含可变状态的Object。
2、保存在共享内存或硬盘上的状态
当然,Erlang肯定要处理现实世界的可变状态,这些可变状态,如果是临时的,通常可以以ets形式保存在内存中,如果变化的周期较长,也可以以dts的形式保存硬盘上。ets和dts是erlang自带的数据库机制。这时,可以说,Erlang以一种基本数据结构的角度来看待包含可变状态的Object。
3、保存在Process中的状态
世界上的事物总是意想不到地复杂的,有时,甚至很多时候,上述两种方式都不太好处理事物的状态。典型的情况是UI,UI的组件需要长时间显示在屏幕上,而这些组件的状态通常是非常复杂的,比如,一个按钮,需要保存它的大小、位置、外观、文字、提示、在点击时可能需要做出的动作,等等等等。由于这类状态的持续性、易变性和异步的特点,以Object的方式来处理似乎是最合适的。目前函数式语言普遍对UI的处理比较弱。
对于这种情况,Erlang的方式是:Process就是Object。
Erlang可以把状态保存在一个Process的Loop循环中,Process之间通过Message来交互。以按钮为例:
- loop(B, Display, Wargs, Fun) ->
- receive
- {event,_,buttonPress,X} ->
- flash(Display, Wargs),
- Fun(X),
- loop(B, Display, Wargs, Fun);
- {event,_,expose,_} ->
- xDo(Display, B),
- xFlush(Display),
- loop(B, Display, Wargs, Fun);
- {onClick, Fun1} ->
- loop(B, Display, Wargs, Fun1);
- {set, Str} ->
- Win = Wargs#win.win,
- xDo(Display, xClearArea(Win)),
- Bin = draw_cmd(Display, Wargs, Str),
- loop(Bin, Display, Wargs, Fun);
- {'EXIT', Pid, Why} ->
- io:format("Here:~p~n",[{self(),Pid,Why}]),
- true;
- Any ->
- Wargs1 = sw:generic(Any, Display, Wargs),
- loop(B, Display, Wargs1, Fun)
- end.
这段代码中可以看到,按钮的状态Wargs(Windows参数)和Fun(登记的动作)通过递归不断地传递(也是保存在调用栈中),并在接受到message时可以做出相应的改变。这时,Erlang的Object不仅具有了状态,而且还能对message做出反应,并且,它在Process中能长时间地维持。
二、Java的OO
这里想谈的是能从Erlang的OO角度来看Java的OO吗?
我的理解是:尽可能。
抛开在Java中的具体实现,假定我们能把Java的Object实例与Erlang的Process类比,那么我们可以:
1、把Object实例的Public方法调用看作是对外部message的反应(恰恰方法调用在OO的本意中原本就是message);
2、把实例方法的调用看作是把函数绑定到(外加到)一堆状态上,相当于Scope在Erlang中的message loop中的函数;
3、把实例的状态看作是在一个Loop中不断被传递的参数。想象有个隐藏在背后的Loop持有这些状态(作为参数),new操作类比为new一个Loop。
4、Class的静态方法是Erlang中的Module方法,相当于Erlang的message loop外的函数。
当然:
1、JVM中的对象实例并不对应着一个share nothing的Process;
2、想象的Loop也不真实存在。
但是,这种转换至少在想象中是可以成立的。而且,它还隐含着一种可能,就是Java的语法可能不需要太多的改变就可以让这种想象变为现实。就是说,如果将来的某一天,JVM的实现真的把每个实例都实现为一个share nothing的process,而且在背后自动地维持着这么一个Loop,再把方法调用实现为message的传递,那么你现在写的Java程序又何尝不能获得Erlang一样的并发优势呢?
有趣的是,SUN确实这么想过,但是由于对性能的忧虑而至少现在没有去做。而且,虽然有人抱怨Java里的基本类型不是对象,但在我看来,这是对的,因为即使在将来,也不可能为每个int i = 1去new 一个进程。何况,通过box,在必要的情况下还是可以把它看作对象的。
这个,也许只是将来。那么,我们现在能做什么呢?
多了解一些函数式编程的知识,并尽量将其用到日常编程中。
对于Java,至少以下几点是现在就可以开始做的:
- 尽量将会调用其它实例的语句与只涉及自身状态的语句分开。我现在通常将所有涉及自身状态的语句放在每个方法的前面,将调用其它实例的语句放在方法的最后面。(本文的最后给出了一个例子)
- 如果一个方法处理的主要是基本数据类型及其容器(list, map等等),那么将它们拿到单独的类中,并设计为static方法。
- 尽量少些setter/getter,可能的话,用一个完整的例程一次性处理完相关的事务。
- 如果其它实例中get出来一个对象,尽量只对它进行只读操作。如果要改变其状态,想想又没有其它更好的方法。
- 如果一个方法可以写成static方法,不妨就写成static方法(可惜java的语法会让static方法签名变得很长)。
最后是一个从我的程序中直接摘出来的一段程序,包括在接触FP之前写的,和最近改的。因为刚学函数编程不久,所以改得不一定合适。
之前:
- public void mouseClicked(MouseEvent e) {
- /** sinlge-clicked ? go on drawing, or, check my selection status */
- if (e.getClickCount() == 1) {
- /** go on drawing ? */
- if (isActivated() && !isAccomplished()) {
- boolean accomplishedNow = anchorHandle(p(e));
- if (accomplishedNow) {
- drawingPane.accomplishedHandledChartChanged(AbstractHandledChart.this);
- }
- /** always set this is selected in this case: */
- getChart().setSelected(true);
- }
- /** else, check my selection status */
- else {
- if (chart.hits(e.getPoint())) {
- if (getChart().isSelected()) {
- final EditAction action = getChart().lookupActionAt(EditAction.class, e.getPoint());
- if (action != null) {
- /** as the glassPane is always in the front, so add it there */
- action.anchorEditor(drawingPane.getView().getGlassPane());
- action.execute();
- }
- }
- getChart().setSelected(true);
- /**
- * I was just selected only, don't call activate() here, let drawingPane
- * to decide if also activate me.
- */
- } else {
- getChart().setSelected(false);
- /**
- * I was just deselected only, don't call passivate() here, let drawingPane
- * to decide if also passivate me.
- */
- }
- }
- }
- /** double clicked, process chart whose nHandles is variable */
- else {
- if (!isAccomplished()) {
- if (nHandles == VARIABLE_NUMBER_OF_HANDLES) {
- anchored = false;
- accomplished = true;
- selectedHandleIdx = -1;
- drawingPane.accomplishedHandledChartChanged(AbstractHandledChart.this);
- }
- }
- }
- }
- public void mouseMoved(MouseEvent e) {
- if (isActivated()) {
- if (!isAccomplished()) {
- if (anchored) {
- stretchHandle(p(e));
- }
- }
- /** else, decide what kind of cursor will be used and if it's ready to be moved */
- else {
- Handle theHandle = getHandleAt(e.getX(), e.getY());
- /** mouse points to theHandle ? */
- if (theHandle != null) {
- int idx = currentHandles.indexOf(theHandle);
- if (idx >= 0) {
- selectedHandleIdx = idx;
- }
- cursor = HANDLE_CURSOR;
- }
- /** else, mouse does not point to any handle */
- else {
- selectedHandleIdx = -1;
- /** mouse points to this chart ? */
- if (chart.hits(e.getX(), e.getY())) {
- setReadyToDrag(true);
- cursor = MOVE_CURSOR;
- }
- /** else, mouse does not point to this chart */
- else {
- setReadyToDrag(false);
- cursor = DEFAULT_CURSOR;
- }
- }
- }
- }
- }
之后:
- public void mouseClicked(MouseEvent e) {
- if (e.getClickCount() == 1) {
- /** sinlge-clicked ? go on drawing, or, check my selection status */
- final boolean willAnchorHandle = isActivated() && !isAccomplished();
- final boolean chartSelected = willAnchorHandle ||
- !willAnchorHandle && chart.hits(e.getPoint());
- final boolean willBeginEdit = !willAnchorHandle && chartSelected;
- chart.setSelected(chartSelected);
- /** following will affect other objects */
- if (willAnchorHandle) {
- final boolean accomplishedNow = anchorHandle(p(e));
- if (accomplishedNow) {
- drawingPane.accomplishedHandledChartChanged(AbstractHandledChart.this);
- }
- }
- if (willBeginEdit) {
- final EditAction action = chart.lookupActionAt(EditAction.class, e.getPoint());
- if (action != null) {
- /** as the glassPane is always in the front, so add it there */
- action.anchorEditor(drawingPane.getView().getGlassPane());
- action.execute();
- }
- }
- } else {
- /** double clicked, process chart whose nHandles is variable */
- if (!isAccomplished() && nHandles == VARIABLE_NUMBER_OF_HANDLES) {
- anchored = false;
- accomplished = true;
- selectedHandleIdx = -1;
- drawingPane.accomplishedHandledChartChanged(AbstractHandledChart.this);
- }
- }
- }
- public void mouseMoved(MouseEvent e) {
- if (isActivated()) {
- if (isAccomplished()) {
- /** decide what kind of cursor will be used and if it's ready to be moved */
- final Handle theHandle = getHandleAt(e.getX(), e.getY());
- if (theHandle != null) {
- /** mouse points to theHandle */
- final int idx = currentHandles.indexOf(theHandle);
- selectedHandleIdx = idx >= 0 ? idx : selectedHandleIdx;
- cursor = HANDLE_CURSOR;
- } else {
- /** mouse does not point to any handle */
- final boolean mousePointToThisChart = chart.hits(e.getX(), e.getY());
- readyToDrag = mousePointToThisChart;
- selectedHandleIdx = -1;
- cursor = mousePointToThisChart ? MOVE_CURSOR : DEFAULT_CURSOR;
- }
- } else {
- if (anchored) {
- stretchHandle(p(e));
- }
- }
- }
- }