新手问题 在画 Ruby 对象继承关系图时没想通 Kernel singleton_methods 存在的目的还有使用场景,求指点!

oatw · July 08, 2019 · Last by oatw replied at July 09, 2019 · 2452 hits

自己画了一个图来表示 Ruby 中对象的继承关系,不确定是不是有疏漏和不正确的地方,路过的前辈如果有空,麻烦帮小弟看看,提前谢过!

另外,在画这个图的过程中有一个问题没想明白,想请教一下:

Kernel 定义了很多便利的 instance methods,可以在对象内部或全局作用域下(也是对象内部,只不过是 Object 内部)通过祖先链查找到进行调用,但是我看到 Kernel 还定义了很多 singleton methods,并且和 instance methods 方法名是一样的,比如print,在继承关系中这些 singleton_methods 貌似是孤立的,在不用其他方式修改 self 的指向时应该只能通过 Kernel.xxx 的形式来调用吧,难道只是为了多提供一种方式来调用类似print这样的方法?还请了解的大佬帮助指点迷津,介绍下 Kernel 中这些与 instance methods 重名的 singleton methods 存在的意义和使用场景,非常感谢!

如下是我简单打印的 Kernel 中命名一样的 instance methods 和 singleton methods:

instance_methods_in_kernel = Kernel.private_instance_methods(false) << Kernel.public_instance_methods(false)
singleton_methods_in_kernel = Kernel.singleton_methods(false)

filter = ->(method_name) {singleton_methods_in_kernel.include? method_name}
methods_with_the_same_name = instance_methods_in_kernel.select(&filter)

p methods_with_the_same_name 

# => 
[
  :sprintf, 
  :format, 
  :Integer, 
  :Float, 
  :String, 
  :Array, 
  :Hash, 
  :warn, 
  :local_variables, 
  :require_relative, 
  :autoload, 
  :autoload?, 
  :raise, 
  :fail, 
  :global_variables, 
  :__method__, 
  :__callee__, 
  :__dir__, 
  :binding, 
  :require, 
  :URI, 
  :eval, 
  :iterator?, 
  :block_given?, 
  :catch, 
  :throw, 
  :loop, 
  :Rational, 
  :Complex, 
  :trace_var, 
  :untrace_var, 
  :at_exit, 
  :set_trace_func, 
  :select, 
  :`, 
  :caller, 
  :caller_locations, 
  :test, 
  :fork, 
  :exit, 
  :sleep, 
  :gets, 
  :proc, 
  :lambda, 
  :load, 
  :exec, 
  :exit!, 
  :system, 
  :spawn, 
  :abort, 
  :syscall, 
  :open, 
  :printf, 
  :print, 
  :putc, 
  :puts, 
  :readline, 
  :readlines, 
  :p, 
  :rand, 
  :srand, 
  :trap
]
1 Floor has deleted

比如 print,在继承关系中这些 singleton_methods 貌似是孤立的,在不用其他方式修改 self 的指向时应该只能通过 Kernel.xxx 的形式来调用吧

你想想下面是调用的哪个方法

Class Foo
 print ""
end
Reply to adamshen

在定义 Foo 这个 class 调用 print 的时候,self 指向 Foo,Foo 相当于一个实例,那这个 print 应该是查找它的类中的实例方法 print,祖先链走的应该是#MyClass(#Foo)这条,会在 Kernel 中找到 print 方法,但 Kernel 中找到的这个 print 是 Kernel 的 instance method 不是 singleton method 中的 print 呀。我的理解对不对呀。

Reply to adamshen

前辈,我为了证明,写了一小段代码,从输出结果来看,我的推测应该是对的呀。

module Kernel
  def print(arg)
    p 'instance method print of Kernel'
  end
  class << self
    def print(arg)
      p 'singleton method print of Kernel'
    end
  end
end

class Foo
  print "" # => "instance method print of Kernel"
end

虽然 Kernel 的 instance method print 和 singleton method print 都是同一个 print 方法对象,但是我还是没能通过你的例子理解 Kernel 中 singleton methods 中和 instance methods 中重名方法的使用场景。我是不是有点笨。。。😟

有时候会需要用到一些白板类,这些类不继承自 Object,这时候如果你想调用 Kernel 的某些方法的话,就要用到 singleton method 了

Reply to pinewong

非常感谢!看到白板类三个字不由自主地激灵了一下子,有点开窍了。

如果你想给 Kernel 打补丁并输出一些调试信息,类似于module Kernel; p @@spike;end,要是 Kernel 没有 p 这样的单例方法,你该咋整嘞。怕是只有 extend self 咯

Reply to spike76

很有道理咧!多谢兄台!

插句嘴,是否会存在全局静态函数?

Reply to bluesky0318

在 Module 类中定义的实例方法会成为所有类以及模块的类方法,不知道是不是你要的效果

class Module
  def foo
    p 'spike'
  end
end
BasicObject.foo # 'spike'
Reply to bluesky0318

兄弟,我对你的问题的理解有两种。

1.顶级作用域下的单件方法

Ruby 顶级作用域就是在 Object 内部,self 默认指向一个 Object 的特殊实例 -> main 对象,在调用方法的时候其实调用的是 main 对象的单件方法, updated_begin: 在调用方法的时候实际上生效的是 Object 的实例方法,但是如果在 self 上显示添加了单件方法,比如像这样 def self.method_name; end, 那么在调用这个单件方法时,就是在其单件类上查找到的 updated_end,查找方法的完整祖先链是:

self.singleton_class.ancestors # => [#<Class:#<Object:0x00000000004ea2c8>>, Object, Kernel, BasicObject]

(其实整个的原理跟在 class 关键字内部定义方法和打开单件类定义方法的原理是一样的,是我一开始没理解好,这里做了修改。)

2.所有对象的单件方法

Ruby 中一切皆对象,按照继承链图谱,要保证所有对象都有某个可被调用的单件方法的话,那么这个单件方法可定义的地方不只一处,可以在 BasicObject 中、Kernel 中、Object 中定义为实例方法。

另外, 如果有 java 背景更强调类的概念,@spike76 兄弟给你的回答应该是更容易理解的。

我在学习 Ruby 的过程中感觉类和对象的界限不是绝对的,类也是对象,对象也有可能是个类,如何区分应该是看它当前扮演的角色,或者说我更倾向于用单件类的概念来理解对象的方法,在 Ruby 中对象的方法是按照其单件类继承链来查找的

(ps:我是初学者,如果有前辈发现有描述错误的,请指正,免得我误导了别人,谢谢!)

Reply to oatw

从C++以及C#语言,主要是内存,堆栈概念来看,类对象方法以及属性只是当这个类对象内存被分配之后,即实例化后存在的,那么事实上,只要提及类对象的方法,一般都是实例化的,跟随内存分配而存在,跟随内存销毁而消失。因此一旦类对象被干掉,所谓的方法以及属性只是一堆符号。 全局方法及属性,如果在自己定义的框架中,是可以强制内存分配的,全部类可见,可用,不会随类对象销毁而销毁。怎么说,还是到内存里面去了,这个内存就当SDK启动就一直存在了。如果我们常用的int,double,byte等,你用它就存在,当然貌似ruby把这些也当做对象在看,而C,C++,C#只是临时给它们分配一些内存。 可能没有说清楚,呵呵,权当看看

Reply to oatw

Sorry。举了个错误的例子,不是你笨,是我笨,你比我更聪明。牛逼👍

Reply to oatw

事实上,现在没有太多的程序员钻研的这么深刻了,都是拿着能用,能开发就好,内存管理需要回到最基础的计算机组成原理,汇编等基础语言上。如果开发过 C 语言,单片机会更深刻一些,当然如果开发过 SDK,Drive 也会感触颇多。

Reply to bluesky0318

因为我不是学计算机出身的,自己的计算机基础也比较薄弱,所以只是在用一些自己觉得比较好理解的笨方法来理解和记忆,兄弟你的阐述我大致看明白了,怎么说呢,就是有一个自己最熟悉的概念模型能帮助自己最好的理解就可以了。我的计算机基础还需加强。

Reply to bluesky0318

大部分人还是停留在应用的层面,可能也没必要太深入的研究,但是不能否认基础好的人对技术的领悟力更强。所以我觉得如果自己需要进阶,计算机基础还是需要恶补的。

Reply to oatw

ruby 官方文档没有这种树形继承结构图么?C++,C#,java 直接跟你拉出来了。

Reply to bluesky0318

官方文档我没怎么翻,我是先看的书,一本叫做《Ruby 元编程》的书,书里给到的继承图谱是一些比较核心部分的,我在网上也没搜索到全面的(可能有,我没发现),所以上面那个图是我在 irb 里根据各个对象打印出来的祖先链画出来的。。。

Reply to adamshen

不不不,程序员都挺聪明的,我只是多了点细心,嘿嘿~

Reply to oatw

其实你在顶级作用域中定义的方法,不能算是 main 对象的单例方法,在 main 中执行self.singleton_methods,结果其实不包含自定义的方法。这个方法其实成为了 Object 类的私有方法,这是 ruby 中实现全局函数的一种形式 (只能用省略 self 的形式去调用,调用的时候看起来像是函数)。

细想了一下,在 Class 类及其祖先中定义的实例方法均能实现全局静态函数的效果,当时是想得简单了。

Reply to spike76

你是对的!顶级作用域下直接定义方法是在 Object 上定义私有实例方法 (相当于打开类定义实例方法),如果明确在 self 上定义方法,才是在 self 的单件类上定义方法(相当于自定义对象上定义方法的语法 def obj.some_method; end),是我的理解不准确。

但是我在下面的代码里又出现了一个小疑问,还麻烦指点一下,谢谢了!

def global_method
end

p self.singleton_methods.grep(/^global_method/)  # => []
p Object.private_instance_methods.grep(/^global_method/)  # => [:global_method]

def self.global_method2
end

p self.singleton_methods.grep(/^global_method/) # => [:global_method2]


# 这里就有个疑问了,在self单件类上咋还有global_method这个方法呢?
p self.singleton_class.private_instance_methods.grep(/^global_method/) # => [:global_method]

p self.singleton_class  # => #<Class:#<Object:0x00000000003ba290>>

Reply to spike76

我来回答自己的问题了,忘记加 false 参数了,所以在调用 self.singleton_class.private_instance_methods 的时候把 Object 上的方法也查找了。。。

这样就好了

p self.singleton_class.private_instance_methods(false).grep(/^global_method/)  # => []

谢谢前辈的指点!

You need to Sign in before reply, if you don't have an account, please Sign up first.