• 2006-11-06

    管窥Ruby——方法定义

    版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
    http://dreamhead.blogbus.com/logs/3776107.html

    在C/C++中,函数虽然不像functional语言中一样,拥有first class的地位,但函数指针的存在,还是让它在一些表达上显得很简洁。在C层次上,定义一个Ruby方法的API是这样的:
    void rb_define_method( VALUE classmod, char *name, VALUE(*func)(), int argc )

    它为类或模块(classmod)定义了一个实例方法,在Ruby层次上的参数名为name,参数个数为argc。通过这个函数,一个Ruby层次上的函数就和C层次上的函数连接到了一起,如同我在《管窥Ruby——Allocator》中谈到两个层次对象之间的关系一样。

    下面是一个例子,它定义了Array#length方法:
    rb_define_method(rb_cArray, "length", rb_ary_length, 0);
    (array.c)

    如果把同样的问题映射到Java之中,又会是怎样的情形呢?

    我们知道,在Java中没有指针的概念,所以,我们不可能像C语言这样,直接用函数(实际上是函数指针)作为参数。通常的替代方案是使用接口(或是基类)作为参数。类似于上面的功能,我们可能会这样定义接口:
    void defineMethod(RubyClass klassmod, String name, RubyMethod method, int argc);

    当我们只有一个方法要实现的时候,或许这种方式与C的方式看不出来什么差别。当我们有铺天盖地的方法要实现,这种定义的差异便显现出来。

    通常,我们会把相关的内容实现在一个类里:
    class K {
    void methodA() {}
    void methodB() {}
    void methodC() {}
    }

    但这样的实现并不能让它和前面的定义友好协作,因为前面那个定义调用一次,只能加入一个方法。
    defineMethod(RubyKClass, "methodA", ???, 0);

    若要把所有的方法都加入其中,仅仅让K继承自RubyMethod是不够的,因为它只有一个接口,只能让一个方法起作用,当然,如果打算在接口内部来做dispatch,那又是另外的故事了。

    一种方法是加入中间层,让中间层负责完成这个任务。
    class MethodAWrapper implements RubyMethod {
    ... run(...) {
    K k = ...
    k.methodA();
    }
    }

    这样定义就变成了这样
    defineMethod(RubyKClass, "methodA", MethodAWrapper, 0);
    defineMethod(RubyKClass, "methodB", MethodBWrapper, 0);
    defineMethod(RubyKClass, "methodC", MethodCWrapper, 0);

    这样的解决方案也带来了问题:方法的Wrapper如何来写。因为Wrapper是真正注入进去的内容,所以,有些实现即便K中没有对应实现,只要其接口可以完成,我们一样可以通过Wrapper实现,换句话说,它起到了一个辅助实现的作用,也就是Java编程中常用的Util。所以,有时候,我们会疑问这个方法放在哪实现更好,是方法中还是Wrapper中?再者,虽然Wrapper通常是一个很薄的层次,但毕竟要写,所以,也是一道繁琐的工序。

    事实上,JRuby在实现的时候,也有这样的问题。下面是一个定义方法底层的接口:
    public void addMethod(String name, ICallable method) ;
    (RubyModule.java)

    这里的ICallable起到的作用与我们前面例子中的RubyMethod是一样的。这个函数是这样调用的:
    public void defineMethod(String name, Arity arity, String java_name) {
    ...
    addMethod(name, new ReflectedMethod(this, builtinClass, java_name,
    arity, visibility));
    }
    (AbstractMetaClass.java)

    ReflectedMethod,看名字就知道,它利用Java中的Reflection。实际上,它正是利用了Reflection对Java的方法进行封装,将Wrapper和方法合二为一,解决我们前面提到的两个问题。

    或许,从开发的角度来说,这种解决方案有一定的优越性,因为它大大的减少了编码量,但客观的事实是,Java的Reflection无论如何在性能上也无法达到普通函数调用的水平,而且,在JRuby中,这种函数会大量的调用,所以,这也是JRuby性能为人诟病的一个根本原因之一。

    出现这种左右两难的原因是Java语言本身的限制,因为在Java中如果不用Reflection的话,我们无法单独操作方法。显然后来者在这个问题上做得比较好,如果使用C#,delegate会让这个问题的解决方案优雅许多。显然,在继续讨论下去的话,就成了《程序设计语言的表达》的延续。

    引用地址:

    评论

  • C#的delegate只是个syntax sugar,本质上仍是用的command pattern,只是编译器自动生成了相关的类而已,并不提高运行效率。
    dreamhead回复yawl说:
    这个地方需要的恰恰是syntax sugar,用它替换解决中间的wrapper,与效率无关。
    2006-11-07 07:26:37