OOP 诡异教程(下)

氷の鋭

2007-08-18 19:56:53 来自: 氷の鋭(神不为者,人为之)

这是最终确定的 JavaScript 基于消息传递编程风格的文章“OOP 诡异教程(上)”的下篇。原来的想法是以风格开头,谈到 JavaScript 的内部机制,但作者 lichray 迟迟没有动键盘,认为不如利用已有的风格做一套机制出来,这样可能更有意义。于是,就有了这个更加“诡异”的下篇。

这篇文章的宗旨是利用我们仅有的“宾谓”语法构造出完整的一套面向对象机制,所以更多代码在更多的时候是不应在实际工作中使用的(也算一种元语言抽象),所以类似效率、代码风格之类的问题反对回帖质疑。

四. 扩展的实现
上文最后给出了一个“看上去很美”的基于消息传递的编程风格,比如构造一个 People 类的代码类似:

function People () {
  var money = 0
  function setMoney (dollars) {
    money = dollars
  }
  function pay (dollars) {
    money -= dollars
  }
  return (function (verb) {
    return eval(verb)
  })
}

有了这样的语法我们就可以描述不少句子了。但是存在一个问题:现实中的 Objects 之间是存在关系的——比如,forrest 是个 IQ 为 75 的傻子,傻子是 People 的一种。而我们仅仅是生搬硬套了一种语法而割裂了这种 "is-a" 关系。现在我们的工作,目的之一就是让这样一个“真切”的世界从我们已有的编程风格的地基上拔地而起。
到底应该怎样做才能使 Fool 产生的对象都能响应 People 的消息呢?我们要给 Fool 产生的对象(也就是返回的那个匿名函数啦)都添加这样一种能力:如果在 Fool 中响应不了消息,那就反馈给 People 响应。

function Fool (iq) {
  var IQ = iq || 0
  function init (iq) {
    IQ = iq
  }
  return (function (verb) {
    try {
      return eval(verb)
    } catch (e) {
      return People()(verb)
    }
  })
}

js> forrest = Fool()
js> forrest('init')(75)
js> forrest('IQ')
75
js> forrest('money')
0

五. 语法扩展和代码生成
这下代码量增加了很多,强迫潜在的使用者们在创建每个类时都这样写那实在是令人抓狂。本来这篇文章应该不提此类问题的解决,但考虑到有益于读者理解“机制”这个抽象概念,这里给出一个可行的方案——把普通的类代码用 Function() 函数重编译为可用的 JavaScript 函数。也就是说,我们能给出类扩展的代码并指定被扩展的类来获取类似上文的代码:

Fool = extend('People()', function (iq){
  var IQ = iq || 0
  function init (iq) {
    IQ = iq
  }
})

为了方便字符串操作,我们希望编译后的代码的参数部分(如 People())都集中出现在一个位置且尽可能便于定位。在函数头添加一句

var origin = People()

当然是可行的,这样还能使 Fool 内部显式引用到其超类。但这样还不够漂亮。我们修改编译后的样例代码为:

function () {
  return (function (origin) {
    var IQ = 0
    function init (iq) {
      IQ = iq
    }
    return (function (verb) {
      try {
        return eval(verb)
      } catch (e) {
        return origin(verb)
      }
    })
  })(People())
}

这个利用参数传递变量的小技巧不值得学习,实际效率不高。但在这篇文章中,这样绑定特殊变量的技术是标准方案。
那么,extend() 函数的实现为:

function extend (originc, code) {
  function argsArea (code) {
    // 题外话,正则表达式也有不值得使用的时候
    return code.slice(code.indexOf('(')+1, code.indexOf(')'))
  }
  function bodyCode (code) {
    // 不用 trim() 了,没事儿找事儿
    return code.slice(code.indexOf('{')+1, code.lastIndexOf('}'))
  }
  function format (body) {
    var objc = bodyCode(function () {
      return (function (verb) {
        try {
          return eval(verb)
        } catch (e) {
        return origin(verb)
        }
      })
    }.toString())
    return 'return (function (origin) {'+body+objc+'})('+originc+')'
  }
  var $ = code.toString()
  return Function(argsArea($), format(bodyCode($)))
}

这样前文提到过的 extend 的实例代码就可以正常运行了,测试代码不再重复。

六. 机制完备化
这样,我们的基于消息传递编程风格的一套面向对象机制就确定下来了。机制是宪法,是语言的根本大法,有了它,我们就可以通过修改代码生成器,很快地给这套机制进行完备化。
想法有很多,例子只举两个。
第一个例子:类的定义中应该能直接引用到将产生的对象 self。答案只有一句话:把返回的那个作为对象的匿名函数命名为 self。
第二个例子:既然是单继承模式,应当存在一个顶层类 AbsObj,使没有指定继承的类自动继承它。答案也只有一句话:在 extend 函数体第一行添加代码:

if (arguments.length == 1) {
  code = originc
  originc = 'AbsObj()'
}

然后手工构造设计 AbsObj 类,为空也无所谓。不过当然了,一般都会给顶层类添加一些全局性质的消息绑定。由于是“底层操作”,基本上都需要修改 extend 函数。做了一个简单的:

function AbsObj () {
  //检测是否能响应此 verb,要再用一次异常处理
  function canHandle(verb){
    try {
      // 别担心这里的 self 会传递不过去
      self(verb)
    } catch (e) {
      return false
    }
    return true
  }
  function toString() {} // 这个搞起来其实很麻烦~`
  var self = function (verb) {
    return eval(verb)
  }
  return self
}

js> Obj=extend(function(){x=5})
js> o=Obj()
js> o('canHandle')('x')
true
js> o('canHandle')('y')
false

文章写完了,小结一下。消息传递的编程不仅仅是一种代码风格,还可以成长为一种完备的机制。这种完备性远不只是这两篇加起来不到300行的文章所能覆盖的(例如非常彻底的“万物皆对象”,因为只要是能响应消息的函数,连接一下 AbsObj 就是合法对象了;类,函数都可以),大家可以试着玩一玩,顺便体会一下这个计算模型的透明和强大。
另外,熟悉函数式编程的朋友可以帮忙思考一下:这样一个基于闭包变换的计算模型实质上是函数式的,再配合动态的函数式的对象级继承(用一个匿名类代换一下)就能在纯 FP 真正下实现 OOP 了。可惜的是每一次更新操作都要重新生成对象,性能代价大了点,不知道大家有什么好想法。

  • 氷の鋭

    2007-08-20 20:22:51 氷の鋭 (神不为者,人为之)

    又沉下去了~``自己顶...

  • veking

    2007-08-26 00:12:30 veking

    看起来好像不太好理解啊!

    我还是先研究一下FP吧!

  • veking

    2007-08-26 00:16:32 veking

    还有个问题不太明白,就是这样编程有什么好处啊?
    代码好像看起来更难理解了,这是不是违背了“高级程序语言”的初衷!?
    这种风格的代码在JS框架中看到过不少,水锐写过框架吗?

  • 氷の鋭

    2007-08-26 15:18:52 氷の鋭 (神不为者,人为之)

    没有任何好处。我研究的是语言本身。我设计编程模型时需要用其它语言来模拟新的编程范式,会从 JS 和 Scheme 中选一个,这回不巧选了 JS 而已,跟框架、易用什么的搭不上关系。
    PS: 实用时写的代码只是消息要变成字符串而已,我自认为我设计的语法还不错啊,差不多是 JS 能到的极致了吧。

  • hillman

    2008-04-24 18:51:31 hillman

    在框架里面这样写还行,如果这个是整站javascript代码风格,我想辞职。 受不住((function damn(){}()(1,2))())(shit),这种风格的代码找谁看啊?

  • 氷の鋭

    2008-04-24 20:15:04 氷の鋭 (神不为者,人为之)

    那个不是“javascript代码风格”,而是一种不错的思维方式。

  • banroo

    2009-01-03 16:23:05 banroo

    top


这个小组的成员也喜欢去   · · · · · · 

上海趣事
上海趣事 (29)
扯淡er
扯淡er (475)
完美web
完美web (763)
WEB标准
WEB标准 (2850)
CSharp
CSharp (20)
AJAX
AJAX (1752)