-
2006-10-30
管窥Ruby——类层次结构
关于一个RClass结构,我已经写了几篇blog来讨论其中的内容,没办法,谁让Ruby是一种面向对象的程序设计语言呢!类是核心的概念之一,而且很多看似魔术的东西,就是靠这些基础的东西支撑起来的。前面的讨论,我们已经见识过方法和变量了,这里就来看一下RClass中的另外一个字段:super。
回忆一下面向对象的特点:封装、继承、多态。super的出现为继承打下了基础,而多态是要靠继承来发挥作用的,所以,super这样一个字段将原本孤立的类联系到了一起,一个庞大的体系得以运转起来。
从这里的定义,我们便不难发现,Ruby是一个单一继承的体系,因为它只能有一个super。
在Ruby中,super并不像看起来那么简单,因为在实际的处理中,有些功能就是通过这个字段做一些手脚来支持的。下面就来看看以包含模块是如何实现的。
前面的讨论已经提到了,Ruby是一个单一继承的体系,通过mixin,它可以达到类似于多重继承的功能,而mixin实际上就是包含了一个模块。下面这段代码便展示了如何实现一个类包含一个模块的方法:
void
rb_include_module(klass, module)
VALUE klass, module;
{
VALUE p, c;
int changed = 0;rb_frozen_class_p(klass);
if (!OBJ_TAINTED(klass)) {
rb_secure(4);
}
if (NIL_P(module)) return;
if (klass == module) return;if (TYPE(module) != T_MODULE) {
Check_Type(module, T_MODULE);
}OBJ_INFECT(klass, module);
c = klass;
while (module) {
int superclass_seen = Qfalse;if (RCLASS(klass)->m_tbl == RCLASS(module)->m_tbl)
rb_raise(rb_eArgError, "cyclic include detected");
/* ignore if the module included already in superclasses */
for (p = RCLASS(klass)->super; p; p = RCLASS(p)->super) {
switch (BUILTIN_TYPE(p)) {
case T_ICLASS:
if (RCLASS(p)->m_tbl == RCLASS(module)->m_tbl) {
if (!superclass_seen) {
c = p; /* move insertion point */
}
goto skip;
}
break;
case T_CLASS:
superclass_seen = Qtrue;
break;
}
}
c = RCLASS(c)->super = include_class_new(module, RCLASS(c)->super);
changed = 1;
skip:
module = RCLASS(module)->super;
}
if (changed) rb_clear_cache();
}
(class.c)前面部分的代码大多用于检查,比如参数是否合法,防止重复包含等等,简化一下,这段代码主要就是一句话:
c = RCLASS(c)->super = include_class_new(module, RCLASS(c)->super);创建一个类,把用来包含模块类的super指向它,我们也看到了在创建这个类的函数使用了原有的super作为参数,不难猜测,通过这样一个语句,我们便把一个创建出来的新类插入到现有的类层次结构之中。下面便是这个函数的实现:
static VALUE
include_class_new(module, super)
VALUE module, super;
{
NEWOBJ(klass, struct RClass);
OBJSETUP(klass, rb_cClass, T_ICLASS);if (BUILTIN_TYPE(module) == T_ICLASS) {
module = RBASIC(module)->klass;
}
if (!RCLASS(module)->iv_tbl) {
RCLASS(module)->iv_tbl = st_init_numtable();
}
klass->iv_tbl = RCLASS(module)->iv_tbl;
klass->m_tbl = RCLASS(module)->m_tbl;
klass->super = super;
if (TYPE(module) == T_ICLASS) {
RBASIC(klass)->klass = RBASIC(module)->klass;
}
else {
RBASIC(klass)->klass = module;
}
OBJ_INFECT(klass, module);
OBJ_INFECT(klass, super);return (VALUE)klass;
}
(class.c)这段代码主要完成了一些赋值,包括我们前面所说的,将类连接到类层次结构中。稍微需要提一下的是,这里对iv_tbl和m_tbl采用了直接赋值,而这两个字段都是指针,这样的赋值方式让两个不同的对象指向了同一个表,也就是说,如果module新增了方法,class同样可以拥有。
或许,我们会有疑问,这样的实现修改了类的继承关系,岂不会有很大的影响。这完全不必担心。我们看到,在这里给这个新类附的类型是T_ICLASS,而不是通常类说具有的T_CLASS。在Ruby的类层次结构中,存在一些“实际中不存在”的类,这里的包含类就是其中一种,这些类在Ruby的层次上根本看不见。当我们需要找一个类的超类时,这些类会被跳过。把它们放到类的层次结构中,查找方法时,我们不必做特殊的处理。
-
2006-10-22
狗不理,我来了!
这些年,我到过一些城市:西安,给我的感觉是回到了古代;上海,代表的着现代;天津,留在我记忆中的是近代。走在天津街头,会让人有种期待,说不准有个拉洋车的从身边飞奔而过,也许会有买报童边跑边买,或许一群进步青年正在集会。虽然对我而言,这些都是电影中镜头,但站在天津街头,周围的建筑会让人有这种错觉。
到过天津三次。第一次是大学时的一年寒假,实在买不到回家直达的车票,因为宿舍有个兄弟家在天津,所以,来这里中转,那一次,归家心切,所以,只扮演了匆匆过客,第一次天津之行在火车站度过。第二次是大学毕业的时候,几个同学相约再最后聚一次,地点选择了天津,那次才算是真正的天津之行。身为地主的兄弟,带我们转了转天津,“近代”天津印象就是在这次留在我的记忆之中。写这篇blog的时候,我刚刚用一个周末完成了第三次天津之行。
北京到天津,距离很近,所以,在北京去拜访天津的兄弟就变棏容易了许多。京津之间的城际列车一个小时一趟,全程七八十分钟,大大削弱了跨越城市的感觉。据说,新的高速铁路正在建设之中,不久之后,京津之间只要半个小时。
这次到天津看到的是一个建设之中的天津,随处可见的是建筑工地,一座座已经初见规模的高楼大厦修正着我对天津的印象。同学很高兴的带着我沿着海河边前行,看棏出,作为一个天津人,他为家乡的变化而自豪。那次到天津,也曾漫步在海河边,搜索记忆,实在没有找回什么值得回忆的地方,这次的海河给我留下很美的感觉,河水清澈,这是治理和建设的成果。最让我感兴趣的是一座步行桥,以玻璃作为原材料,脑子里胡思乱想着这种东西是否结实,有几块是透明玻璃,可以清楚看见脚下的东西,站在上面,淘气的心让我有种想蹦蹦检查玻璃质量的冲动,为了自己的安全,作罢。“这原来是奥匈帝国的租界”,同学指着一片楼对我说,看着那里,思绪又开始飘到近代,天津有很多这种地方,建筑风格一看就是距今已有很多年的历史。我下车的天津北站,是一个难得的拍电影电视的好地方,因为现代都市中很难找到这种有些历史的地方,所以,坐火车到天津,会给人一种天津很可怜的感觉,火车站使然。天津有着悠久的历史和文化,在古文化街里,可以看到很多有趣的东西,一进门,便看到一个洋车,只是上面坐着的是一个肌肤黝黑的外国人。
关于天津,最早的印象是与吃有关。没到过天津,但我知道狗不理包子,十八街麻花。上次逛天津,最让我回味的便是狗不理包子。这次回来,买了不少东西:狗不理包子、十八街麻花、耳朵眼炸糕和崩豆,哈哈,又有好吃的东西了!
-
2006-10-17
管窥Ruby——类的变量
UPDATE
这篇blog的叙述存在一些偏差,所以,又重新写过:《管窥Ruby——类的变量(更新版)》变量和方法是面向对象难以割舍的两个重要组成部分。在《管窥Ruby——类的方法》中,我们谈到方法,沿着这条路继续,我们再来看看类中的变量。
开始之前,我们还是要再次回顾RClass的定义:
struct RClass {
struct RBasic basic;
struct st_table *iv_tbl;
struct st_table *m_tbl;
VALUE super;
};
(ruby.h)如果你看过《管窥Ruby——类的方法》,了解了方法存储方式,变量的存储方式便也一目了然了,同样的st_table,意味着同样的处理方式。
不得不承认的一点是,在讨论类的方法时,我故意忽略了一个事实:方法分为类的方法和实例的方法两种。如果对其它语言实现有些许了解,我们知道,这两种方法差别仅仅是this(C++或Java的说法),到了底层时,这个差别可以视为无物,可以统一存放。关于这点,有兴趣可以参考一下《深入Java虚拟机》,它的第五章讲解了虚拟机内方法的表现形式。
但是,当我们遇到变量时,同样的问题,我们便不能忽略了:类的变量和实例变量是无法统一管理的,因为类的变量只有唯一的一份,而实例变量则是每个实例都有一份。代码是说明问题的最好方式,下面这段代码说明了如何设置一个实例变量:
VALUE
rb_ivar_set(obj, id, val)
VALUE obj;
ID id;
VALUE val;
{
if (!OBJ_TAINTED(obj) && rb_safe_level() >= 4)
rb_raise(rb_eSecurityError, "Insecure: can't modify instance variable");
if (OBJ_FROZEN(obj)) rb_error_frozen("object");
switch (TYPE(obj)) {
case T_OBJECT:
case T_CLASS:
case T_MODULE:
if (!ROBJECT(obj)->iv_tbl) ROBJECT(obj)->iv_tbl = st_init_numtable();
st_insert(ROBJECT(obj)->iv_tbl, id, val);
break;
default:
generic_ivar_set(obj, id, val);
break;
}
return val;
}
(variable.c)抛开前面那些复杂的东西,直接来看switch语句后面的内容。在这里,我们看到只有在特定的情况下,才会向iv_tbl中写入内容,其它情况则交给了generic_ivar_set处理。观察一下这些"特定"情况,我们不难发现,实际上,iv_tbl中存放的是类的变量,而非实例的变量。所以,这个名为“实例变量表(instance value table)”的变量,多少有些名不符实。
类的变量找到了落脚点,那实例的变量放在了哪里。别忘了,后面还有一个generic_ivar_set。下面是generic_ivar_set的实现:
static void
generic_ivar_set(obj, id, val)
VALUE obj;
ID id;
VALUE val;
{
st_table *tbl;if (rb_special_const_p(obj)) {
special_generic_ivar = 1;
}
if (!generic_iv_tbl) {
generic_iv_tbl = st_init_numtable();
}if (!st_lookup(generic_iv_tbl, obj, (st_data_t *)&tbl)) {
FL_SET(obj, FL_EXIVAR);
tbl = st_init_numtable();
st_add_direct(generic_iv_tbl, obj, (st_data_t)tbl);
st_add_direct(tbl, id, val);
return;
}
st_insert(tbl, id, val);
}
(variable.c)同样忽略一些非主干的部分,我们看到,这段代码先在一个generic_iv_tbl中进行查找,用作查找键值的是对象实例(obj),而目标同样是一个st_table。得到这个表之后,利用变量名做键值将值插入到表中。
由此,我们便不难分析出实例变量的存储方式。存在一个全局的实例变量表,所有实例都在其中拥有一席之地,而实例变量存储在实例对应的表中。Ruby就是用了这样一个二级结构,解决了实例变量存储的问题。
UPDATE
事实上,上面的说法并不完全正确,rb_ivar_set中真正的主干是T_OBJECT,因为在Ruby层次上定义的类,走的都是这条路,这一点可以从初始化的代码中看到。另外,上面没有提到类变量如何处理,实际上,在Ruby中,类也是对象,类的变量实际上就是类对象的实例变量。
-
2006-10-16
根据类名创建实例——Ruby实现
在Ruby On Rails中文社区的的Ruby版上,karl问了一个有趣的问题,如何根据类名生成一个类的实例。
链接在这里:
http://www.railscn.com/about2289.html下面是一个Java语言的实现:
public static Object createObject(String className)
throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Class clazz = Class.forName(className);
return clazz.newInstance();
}在Ruby中怎么做呢?karl自己给出了第一种做法:
str = 'String'
eval "obj=#{str}.new"这里利用了Ruby的动态语言的特性,在代码中执行代码。不过,照着尝试了一下,我有了一个疑问,如何使用生成的对象,也就是,我们如何利用这里的obj呢?
按照karl提供的方式,编写出这样的代码:
str = 'String'
eval "obj = #{str}.new"
p obj这里故意使用了p方法,因为对于一个空串,它可以打出"",而不是无声无息,便于查看。
这样的代码是无法运行的,异常如下:
undefined local variable or method `obj' for main:Object (NameError)然而,这段代码在irb中执行没有问题,这是irb和Ruby不兼容的一个地方。在《Programming Ruby》第二版中的第15章,谈及irb限制的时候,提到了这一点。
eval执行会有一个值,也就是它最后执行的语句的值,对应到这里就是obj的值。根据这个特性,改写得到了第二种做法,代码如下:
str = 'Array'
obj = eval "obj = #{str}.new"
p obj显然eval里的obj没有发挥什么作用,再修改一下:
str = 'Array'
obj = eval "#{str}.new"
p obj这样,我们就根据字符串创建出一个可用的对象。事实上,第一种方式也是可用的,不过,要obj在前面已经存在,只不过要稍做修改:
obj = nil
eval "obj = String.new"
p objqiezi的回帖给出了一个我认为最漂亮的一种做法:
obj = eval(str).new同样是利用eval,不同的是,这里是直接执行类名,结果是得到一个类对象。调用类的new方法就创建实例。这段代码看上去类似于一个普通的函数调用,很简洁。
对应到前面的Java函数,Ruby的实现就可以这样写:
def create_obj(class_name)
eval(class_name).new
endUPDATE
故事有了新的进展,njmzhang提供了另外的方法,按照他的说法,这几乎是一种标准用法:
c = Object.const_get("Array")
s = c.new这里利用到了一个事实,所有的类对象在Ruby中都是一个常量,所以,可以根据它的名字把这个常量提取出来。
除此之外,njmzhang还给出了另一种利用扩展实现的方法:
c = Class.by_name("Array")这里用到的扩展在
http://extensions.rubyforge.org/从方法的命名上来说,它比上一种方法更恰当一些。
算上这两种实现,我最喜欢的仍然是qiezi给出的那种方法。
-
2006-10-14
长城一日游
“不到长城非好汉”,不知道毛主席他老人家说这句话的时候,是否考虑过为长城的旅游事业做贡献。至少现实的结果是,很多人为了与孬种划清界限,便专程到长城一游。能想到与此话有着类似功效的,大概就是赵本山那句“大城市”吧!
前前后后,在北京的时间并不算短,但长城却始终没去过。毛主席的那句话始终萦绕在心头,于是,长城就成了我最想去的地方。十一之前,与一个朋友谈及此事,他居然与我有同样的想法,二人一拍即合,长假归来便要去当回“好汉”。
长城游的感受如下:
好身体很重要。至少长城回来,身体还不是特别疲惫。比起那年爬香山,年龄最小却最后一个爬到山顶的尴尬强多了。由此证明,锻炼还是有一定作用的。
北京出游,交通是问题。到长城,坐919,12元,这是我坐过的最贵的公交车。在北京出门,坐上一个小时以上的公交车是常态,如果赶上哪位神仙心情不好,堵在路上也是干瞪眼,不太喜欢这种交通不便。回程的路上,因为无知,在一个陌生的地方下了车,结果比预计的多用了一个小时才到家。
朋友认为,刚过十一,出游的人应该不多,事实证明,中国人还是足够多。
爬长城,让我想起了当年登泰山,一下望不到顶,高高兴兴登上了一段,抬头一看,还有更高峰。
入口分为南北两个方向,从地图上看,北面的垛口要比南面的多,所以,往北的人明显比往南的人多。
有一条死路,必须回头,结果,几乎所有人都不相信,非要亲自碰一下“南墙”再回头,包括我们。
有一个特别陡的坡,据说有80度。过了之后,我和朋友在想,当年没有边上的扶手,这段路得怎么走呢?
拍照如果不能把握好时机,会分不清主角是谁,尤其在那个888米的地方——据说的最高点。不过,这也给偷拍者留下机会。依山势建的长城,真的很壮观,尤其是站在高处向下望的时候,心情舒畅!







