向’self’发送消息会导致调用initialize方法吗?
我有两个classFoo
和Bar
:
require 'pry-byebug' require 'fileutils' class Foo < Pathname include FileUtils def initialize(path) puts "Inside Foo init..." super puts "Side effect happening..." end def some_method puts "Inside some_method inside Foo..." basename.to_s end end class Bar < Foo end bar = Bar.new('bar') # binding.pry bar.some_method
这是输出:
Inside Foo init... Side effect happening... Inside some_method inside Foo... Inside Foo init... Side effect happening...
正如你所看到的那样,“副作用”正在发生两次。 看一下pry-byebug
会话确认:
Inside Foo init... Side effect happening... From: /Users/max/Dropbox/work/tmp/super_test/foo.rb @ line 23 : 18: class Bar 23: bar.some_method [1] pry(main)> step From: /Users/max/Dropbox/work/tmp/super_test/foo.rb @ line 13 Foo#some_method: 12: def some_method => 13: puts "Inside some_method inside Foo..." 14: basename.to_s 15: end [1] pry(#)> step Inside some_method inside Foo... From: /Users/max/Dropbox/work/tmp/super_test/foo.rb @ line 14 Foo#some_method: 12: def some_method 13: puts "Inside some_method inside Foo..." => 14: basename.to_s 15: end [1] pry(#)> step From: /Users/max/Dropbox/work/tmp/super_test/foo.rb @ line 7 Foo#initialize: 6: def initialize(path) => 7: puts "Inside Foo init..." 8: super 9: puts "Side effect happening..." 10: end
所以打破它:
- 我实例化
bar
,它是一个inheritance自Foo
的Bar
实例。Bar
的超类’initialize
被调用,并且“副作用”发生。 到目前为止,这完全是预期的。 - 我在没有它的
bar
打电话给some_method
所以ruby上升到右边并且发现它在Foo
里面 - Ruby在
some_method
并找到一个向self
发送消息的方法,称为basename
- Ruby回到了
Foo
的’initialize
方法?…
第4步让我完全惊讶。 为什么要向self
发送消息会导致initialize方法再次被调用? 这在任何地方记录? 这是预期的吗?
有可能控制这个吗? 或者有条件地检查我是否在initialize方法中,因为我实际上是在实例化一个类,而不是随机地在那里登陆? 例如:
class Foo < SomeClass def initialize args @args = args if instantiating_a_class? puts "Side effect happening..." else puts "Don't do anything..." end end end
为什么要向self发送消息会导致initialize方法再次被调用? 这在任何地方记录? 这是预期的吗?
这就是basename
的实现方式,它返回一个新实例:
/* * Returns the last component of the path. * * See File.basename. */ static VALUE path_basename(int argc, VALUE *argv, VALUE self) { VALUE str = get_strpath(self); VALUE fext; if (rb_scan_args(argc, argv, "01", &fext) == 0) str = rb_funcall(rb_cFile, rb_intern("basename"), 1, str); else str = rb_funcall(rb_cFile, rb_intern("basename"), 2, str, fext); return rb_class_new_instance(1, &str, rb_obj_class(self)); }
最后一行相当于调用new
。
您可以轻松validation:
class Foo < Pathname def initialize(path) puts "initialize(#{path.inspect})" super end end foo = Foo.new('foo/bar/baz') # prints initialize("foo/bar/baz") #=> # foo.basename # prints initialize("baz") #=> #
正如你所说,它与basename
方法有关。 从文档的源代码中可以看出, basename
实例化了该类的另一个对象。
Pathname实例方法通常返回Pathname实例。 为此,他们需要在当前类上调用initialize
。
如果你看一下basename
的源代码:
return rb_class_new_instance(1, &str, rb_obj_class(self));
如果它不是Foo
和Bar
类的所需function,则可以停止inheritancePathname
,并定义@pathname
实例变量。
最后,你可能不希望像昨天提议的那样在initialize
自动创建目录:
获取file.txt
的基本名称可以创建file
目录。
第4步让我完全惊讶。 为什么要向self发送消息会导致initialize方法再次被调用? 这在任何地方记录? 这是预期的吗?
嗯,是。 允许方法调用其他方法。 这几乎是方法的重点。 basename
返回一个新的Pathname
对象。 那么,您认为它如何构建这个新的Pathname
对象? 当然,它调用self.class::new
(实际上是Class#new
),它又调用Pathname#initialize
。
这就是Rubinius实现Ruby标准库时Pathname#basename
#basename的实现 :
def basename(*args) self.class.new(File.basename(@path, *args)) end
Class#new
的实现大致如下:
class Class def new(*args, &block) # allocate a new empty object from the ObjectSpace obj = allocate # initialize it (must use send because initialize is private) obj.send(:initialize, *args, &block) # return object that was initialized obj end end