我想更好地理解Ruby中的对象如何具有在类和模块中定义的访问方法。具体来说,我想与JavaScript(我更熟悉)进行比较和对比。
在JavaScript 中,对象会在对象本身上查找方法,如果无法在那里找到它,它会在原型对象上查找方法。此过程将一直持续到Object.prototype
。
// JavaScript Example
var parent = {
someMethod: function () {
console.log( 'Inside Parent' );
}
};
var child = Object.create( parent );
child.someMethod = function () {
console.log( 'Inside Child' );
};
var obj1 = Object.create( child );
var obj2 = Object.create( child );
obj1.someMethod(); // 'Inside Child'
obj2.someMethod(); // 'Inside Child'
在JavaScript示例中,obj1
和obj2
都没有对象本身的someMethod
函数。需要注意的关键是:
someMethod
对象中child
函数的一个副本,obj1
和obj2
都委托给child
对象。< / LI>
obj
和obj2
都没有对象本身的someMethod
函数的副本。child
对象没有定义someMethod
函数,则委派将继续到parent
对象。现在我想将其与Ruby中的类似示例 :
进行对比# Ruby Example
class Parent
def some_method
put 'Inside Parent'
end
end
class Child < Parent
def some_method
puts 'Inside Child'
end
end
obj1 = Child.new
obj2 = Child.new
obj1.some_method # 'Inside Child'
obj2.some_method # 'Inside Child'
以下是我的问题:
obj1
和obj2
是否拥有some_method
方法的副本?或者它是否类似于JavaScript,其中两个对象都可以通过另一个对象访问some_method
(在这种情况下,通过Child类)?我的直觉告诉我,Ruby对象不要具有从其类,混合模块和超类继承的方法的单独副本。相反,我的直觉是Ruby处理类似于JavaScript的方法查找,其中对象检查对象本身是否具有方法,如果没有,它在对象的类,混合模块和超类中查找方法,直到查找到达BasicObject
。
答案 0 :(得分:2)
让我们继续在IRB会话中使用您的示例,看看我们可能会学到什么:
> obj1.method(:some_method)
=> #<Method: Child#some_method>
> obj1.method(:some_method).source_location
=> ["(irb)", 8]
> obj2.method(:some_method)
=> #<Method: Child#some_method>
> obj2.method(:some_method).source_location
=> ["(irb)", 8]
好的,所以同一个类的两个对象具有相同的方法。我想知道这是否真的......
> obj1.instance_eval do
> def some_method
> puts 'what is going on here?'
> end
> end
=> nil
> obj1.some_method
what is going on here?
=> nil
> obj2.some_method
Inside Child
=> nil
> obj1.method(:some_method)
=> #<Method: #<Child:0x2b9c128>.some_method>
> obj1.method(:some_method).source_location
=> ["(irb)", 19]
这很有趣。
詹姆斯·科格兰(James Coglan)有一篇很好的博客文章,提供了比https://blog.jcoglan.com/2013/05/08/how-ruby-method-dispatch-works/ 更好的解释。考虑这一点何时重要可能也很有趣。想想这个系统有多少是解释器的实现细节,并且可以在MRI,JRuby和Rubinius中以不同的方式处理,以及实际上需要保持一致的Ruby程序在所有这些系统中一致地执行。
答案 1 :(得分:1)
- Ruby代码每个中的
醇>obj1
和obj2
是否拥有some_method
方法的副本?或者它是否类似于JavaScript,其中两个对象都可以通过另一个对象访问some_method
(在这种情况下,通过Child类)?
你不知道。 Ruby语言规范简单地说&#34;如果你做这个, 发生&#34;。但是,它没有规定使 发生的特定方式。每个Ruby实现都可以按照它认为合适的方式自由实现,只要结果与规范的结果相符,规范就不关心 那些结果是什么。
你无法说出来。如果实现保持适当的抽象,那么你就不可能告诉他们是如何做到的。这只是抽象的本质。 (实际上,它几乎就是抽象的定义。)
- 类似地,当在Ruby中考虑继承时,每个Ruby对象是否都具有同名的所有类和超类方法的副本?
醇>
与上述相同。
目前有一些很多的Ruby实现,过去在(完整性)的各个阶段都有更多。其中一些实现(编辑)他们自己的对象模型(例如MRI,YARV,Rubinius,MRuby,Topaz,tinyrb,RubyGoLightly),有些人坐在他们试图适合的现有对象模型之上(例如XRuby和JRuby on CLI上的Java,Ruby.NET和IronRuby,SmallTalk上的SmallRuby,smalltalk.rb,Alumina和MagLev,Objective-C / Cocoa上的MacRuby和RubyMotion,Parrot上的Cardinal,ActionScript / Flash上的Red Sun,SAP / ABAP上的BlueRuby ,ECMAScript上的HotRuby和Opal.rb)
谁能说所有这些实现都完全相同?
我的直觉告诉我,Ruby对象不要具有从其类,混合模块和超类继承的方法的单独副本。相反,我的直觉是Ruby处理类似于JavaScript的方法查找,其中对象检查对象本身是否具有方法,如果没有,它在对象的类,混合模块和超类中查找方法,直到查找到达
BasicObject
。
尽管我上面写的, 是一个合理的假设,实际上,我所了解的实现(MRI,YARV,Rubinius,JRuby,IronRuby,MagLev,Topaz)是如何工作的
只要想想如果不那么它意味着什么。 String
类的每个实例都需要拥有自己的所有String
116方法的副本。想想典型的Ruby程序中有多少String
个!
ruby -e 'p ObjectSpace.each_object(String).count'
# => 10013
即使在这个最简单的程序中,它们不会require
任何库,并且只创建一个单独的字符串(用于将数字打印到屏幕上),已经> em>超过10000个字符串。其中每一个都有自己的100多个String
方法的副本。这将是对记忆的巨大浪费。
这也是同步的噩梦! Ruby允许您随时使用monkeypatch方法。如果我重新定义String
类中的方法怎么办? Ruby现在必须更新该方法的每个副本,即使是跨越不同的线程。
我实际上只计算了String
中直接定义的公共方法。考虑到私有方法,方法的数量甚至更大。当然,还有继承:字符串不仅需要String
中每个方法的副本,还需要Comparable
,Object
,{{{{}}}中每个方法的副本。 1}}和Kernel
。你能想象系统中的每个对象都有BasicObject
的副本吗?
不,它在大多数Ruby实现中的工作方式都是这样的。对象具有标识,实例变量和类(在静态类型的伪Ruby中):
require
模块有方法字典,常量字典和类变量字典:
struct Object
object_id: Id
ivars: Dictionary<Symbol, *Object>
class: *Class
end
一个类就像一个模块,但它也有一个超类:
struct Module
methods: Dictionary<Symbol, *Method>
constants: Dictionary<Symbol, *Object>
cvars: Dictionary<Symbol, *Object>
end
当您在对象上调用方法时,Ruby将查找对象的struct Class
methods: Dictionary<Symbol, *Method>
constants: Dictionary<Symbol, *Object>
cvars: Dictionary<Symbol, *Object>
superclass: *Class
end
指针并尝试在那里找到该方法。如果它没有,它将查看类的class
指针等等,直到它到达没有超类的类。此时它实际上不会放弃,而是尝试在原始对象上调用superclass
方法,将您尝试调用的方法的名称作为参数传递,但这只是一种常规方法也是调用,所以它遵循所有相同的规则(除非对method_missing
的调用到达层次结构的顶部,它将不会再次尝试调用它,这将导致无限循环)。
哦,但我们忽略了一件事:单身方法!每个对象也需要有自己的方法字典。实际上,除了类之外,每个对象都有自己的私有单例类:
method_missing
因此,方法查找在单例类中首先启动 ,只有然后才会进入该类。
那么mixins呢?哦,对,每个模块和类还需要一个包含mixin的列表:
struct Object
object_id: Id
ivars: Dictionary<Symbol, *Object>
class: *Class
singleton_class: Class
end
现在,算法是:首先在单例类中查找,然后在类中查找,然后在超类中查看,然而,#34;看起来&#34;也就是说,在你查看方法字典之后,还要查看所包含的mixins的所有方法字典(以及所包含的mixin的包含的mixins,依此类推,递归)在之前直到超类&#34;。
这听起来有多复杂吗?它是!这并不好。方法查找是面向对象系统中最常执行的算法,它需要简单且快速。因此,一些Ruby实现(例如MRI,YARV)所做的是解释解释器的内部概念,即什么&#34; class&#34;和&#34;超类&#34;从程序员对这些相同概念的看法来看。
一个对象不再具有单例类和类,它只有一个类:
struct Module
methods: Dictionary<Symbol, *Method>
constants: Dictionary<Symbol, *Object>
cvars: Dictionary<Symbol, *Object>
mixins: List<*Module>
end
struct Class
methods: Dictionary<Symbol, *Method>
constants: Dictionary<Symbol, *Object>
cvars: Dictionary<Symbol, *Object>
superclass: *Class
mixins: List<*Module>
end
一个类不再包含一个包含mixins的列表,只是一个超类。然而,它可能是隐藏的。还要注意字典成为指针,你马上就会明白为什么:
struct Object
object_id: Id
ivars: Dictionary<Symbol, *Object>
class: *Class
singleton_class: Class
end
现在,对象的类指针将始终指向单例类,而单例类的超类指针将始终指向对象的实际类。如果将mixin struct Class
methods: *Dictionary<Symbol, *Method>
constants: *Dictionary<Symbol, *Object>
cvars: *Dictionary<Symbol, *Object>
superclass: *Class
visible?: Bool
end
包含到类M
中,Ruby将创建一个新的不可见类C
,它与mixin共享其方法,常量和cvar词典。这个mixin类将成为M′
的超类,而C
的旧超类将成为mixin类的超类:
C
实际上,它有点涉及,因为它还必须为M′ = Class.new(
methods = M->methods
constants = M->constants
cvars = M->cvars
superclass = C->superclass
visible? = false
)
C->superclass = *M'
(和递归)中包含的mixin创建类,但最后,我们最终得到的是一个不错的线性方法查找路径,没有单步进入单例类并包含mixins。
现在,方法查找算法就是这样:
M
美观,干净,精益,快速。
作为权衡,找出一个对象的类或一个类的超类稍微困难一些,因为你不能简单地返回类或超类指针,你必须走链子直到你找到一个未隐藏的类。但是,您多久拨打一次def lookup(meth, obj)
c = obj->class
until res = c->methods[meth]
c = c->superclass
raise MethodNotFound, meth if c.nil?
end
res
end
或Object#class
?在调试之外你甚至可以打电话吗?
不幸的是,Class#superclass
并不适合这张照片。并且精炼真的搞砸了,这就是许多Ruby实现甚至不实现它们的原因。
答案 2 :(得分:0)
更多值得思考的东西
> obj1.instance_eval do
> def some_method
> puts "Inside Instance"
> super
> end
> end
=> :some_method
Inside Instance
Inside Child