在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
本节书摘来自华章出版社《Effective Ruby:改善Ruby程序的48条建议》一书中的第2章,第2.1节,作者 [美]彼得 J.琼斯(Peter J. Jones),更多章节内容可以访问云栖社区“华章计算机”公众号查看 第6条:了解Ruby如何构建继承体系问题:当你向一个对象发送消息时,你知道Ruby是怎么定位到那个正确的方法的吗?答案看上去很简单:使用继承体系。之所以说答案是看上去简单,因为Ruby构建继承体系是在幕后进行的。这是Ruby用自己的方式来隐藏真正发生的事情的罕见情形之一,它常常带来不必要的困惑。Ruby提供给我们的用来发现继承关系中的类的方法并能完全解释其本质。还好,Ruby解释器在内部构建的继承体系是一致而简单的,只要你明白一些它的技巧。 这段代码没有什么让人惊讶的。如果你调用customer对象的name方法,方法的查找会和你预期的完全一样。首先,Customer类会检查自身是否有这个实例方法。显然没有,于是继续搜索,顺着继承体系向上找到了Person类,在该类中找到了该方法并将其执行。如果在Person类中没有找到的话,Ruby会继续向上查找直到找到该方法或者到达根类BasicObject。你可以从! 如你所知,如果方法在查找过程中直到类树的根节点仍然没有找到匹配的方法,那么它将重新从起点开始查找,不过这一次会查找method_missing方法。第30条会请你自行定义method_missing方法,因此这里我们不再涉及。也就是说,除了Kernel模块中method_missing的默认实现会引发异常告诉你说你调用了一个未定义的方法以外,不会发生别的什么了。 我已经将name方法从Person类中取出并移到一个模块中,并将模块引入了Person类。Customer类的实例仍然可以如你所料响应name方法,但是为什么呢?显然,模块ThingsWithNames并不在继承体系中,因为Person类的超类仍然是Object类,那会是什么呢?其实,Ruby在这里对你撒谎了,我们需要多讲一点单例类。 当你使用include方法来将模块引入类时,Ruby在幕后悄悄地做了些事情。它创建了一个单例类并将它插入类体系中。这个匿名的不可见类被链向这个模块,因此它们共享了实例方法和常量。本例中Person类在包含模块ThingsWithNames时,Ruby创建了一个单例类并悄悄地作为Person类的超类插入了继承体系之中。你会说,“但是调用Person的superclass方法返回的是Object而不是ThingsWithNames啊。”是的,这是因为:单例类是匿名而不可见的,因此super-class和class方法都会跳过它。所以,一个更精确的类体系也需要包含模块,如图2-2所示。 当每个模块被类包含时,它会立即被插入继承体系中包含它的类的上方,以后进先出(LIFO)的方式。每个对象都通过变量superclass连接,像单向链表一样。这唯一的结果就是,当Ruby寻找一个方法时,它将以逆序访问每个模块,最后包含的模块最先访问到。很重要的一点是,模块永远不会重载类中的方法。因为模块插入的位置是包含它的类的上方,而Ruby总是会在向上检查之前先检查类本身。(好吧……这不是全部的事实。确保你阅读了第35条,来看看Ruby 2.0中的prepend方法是如何使其复杂化的。) 既然你已经适应了模块和单例类间的交互,那么我的责任是改变点什么从而让困难来得更猛烈(但可爱)一点: 如果你以前没见过这种语法,你可能会有点困惑,但这其实很明确。上面的代码定义了仅用于customer对象的一个方法。这个特定的方法在其他任何对象上都不能调用。你觉得Ruby是怎样实现它的呢?如果你指出单例类,那么你说对了。事实上,这个方法被称为单例方法。当Ruby执行这段代码时,它将创建一个单例类,将name方法作为其实例方法,随后将这个匿名类作为customer对象的类进行插入。不过即使此时customer对象的类是单例类,Ruby的内省方法class方法仍将跳过它并返回Customer。这让我们觉得难以理解,但让Ruby的实现变得简单。当它查找name方法时,仅需要遍历类的结构就可以了。不需要特殊的逻辑。 这也透露了一些为什么这种类被称为单例类的线索。多数类服务很多对象,比如Array类包含你的程序中用到的每个数组对象使用的方法。而单例类仅仅服务一个对象。 除了类方法以外,我没有更多的花样可以描述了。你将很高兴地发现你可能已经理解它们了。它们是我们已经探索过的单例类的另一种表现形式。 注意,当你定义单例方法时,你指定了一个对象(可以认为是接收者),它将是这个对象的所有者。当你定义一个类方法时,你通过定义类对象做了完全相同的事情,不论是通过类名还是更常见的self变量。对象毕竟是对象,即使它是类。因此类方法也是作为单例类的实例方法存储的。自己来看: 最后,当Ruby希望查找一个方法时,只需要从继承体系着手。不论是实例方法还是类方法都是这样。一旦你理解了Ruby是如何操作继承体系的,那么理解查找方法的过程就非常简单了: 1.?找到接收者的类,实际上它可能是一个隐藏的单例类。(记住,你在Ruby代码中不能这样做,因为class方法会跳过单例类。你得直接调用singleton_class方法。) 2.?查找该类中存储的实例方法列表。如果找到该方法就停止搜索并执行代码。否则,继续第3步。 3.?顺着继承体系向上找到其超类并重复第2步。(这个超类也可能是另一个单例类。在Ruby中,通过superclass方法,它可能是不可见的。) 4.?重复第2步和第3步直到Ruby找到这个方法或者抵达体系的顶点。 5.?当抵达顶点时,Ruby回到原来的接收者的第1步的地方继续查找,不过这回找的是method_missing方法。 如你所见,当方法存储于不可见的单例类时,在代码中很难明确地看到整个继承体系。但是你能使用Ruby为了内省而提供的几个方法来拼出完整的场景: singleton_class方法返回接收者的单例类,如果不存在就创建它。 ancestors方法返回组成了继承体系的所有类和模块的数组。它只能在类和模块上被调用,并会跳过单例类。 included_modules方法返回和ancestors方法一样的数组,不过其中所有的类都被过滤掉了。 还好,你很少会使用这些方法。明白继承体系的构建和内部搜索原理已经足够解决大多数情况了。如果需要的话,第35条包含了一些例子。 要点回顾 要寻找一个方法,Ruby只需要向上搜索类体系。如果没有找到这个方法,就从起点开始搜索method_missing方法。 包含模块时Ruby会悄悄地创建单例类,并将其插入在继承体系中包含它的类的上方。 单例方法(类方法和针对对象的方法)存储于单例类中,它也会被插入继承体 系中。 |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论