我有两个课程Foo
和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 < Foo
19: end
20:
21: bar = Bar.new('bar')
22: binding.pry
=> 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(#<Bar>)> 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(#<Bar>)> 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
,这是Bar
的一个实例,它继承自Foo
。调用Bar
的超类“initialize
并发生”副作用“。到目前为止,这完全是预期的。some_method
上致电bar
所以ruby上升到右边并在Foo
some_method
内跳转并找到一个向self
发送消息的方法basename
Foo
的'initialize
方法?... 第4步让我完全惊讶。为什么要向self
发送消息会导致初始化方法再次被调用?这记录在哪里?这是预期的吗?
可以控制吗?或者有条件地检查我是否在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
答案 0 :(得分:4)
为什么向自己发送消息会导致初始化方法再次被调用?这记录在哪里?这是预期的吗?
实现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
。
您可以轻松验证:
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:foo/bar/baz>
foo.basename
# prints initialize("baz")
#=> #<Foo:baz>
答案 1 :(得分:2)
正如您所说,它与basename
方法有关。正如在documentation的源代码中可以看到的那样,basename
实例化了该类的另一个对象。
答案 2 :(得分:2)
Pathname实例方法通常返回Pathname实例。为此,他们需要在当前班级上调用initialize
。
如果你看一下basename
的源代码:
return rb_class_new_instance(1, &str, rb_obj_class(self));
如果它不是Foo
和Bar
类所需的功能,则可以停止继承Pathname
,并定义@pathname
实例变量。
最后,您可能不希望像昨天提议的那样在initialize
中自动创建目录:
获取file.txt
的基本名称可以创建file
目录。
答案 3 :(得分:2)
第4步让我完全惊讶。为什么要向self发送消息会导致initialize方法再次被调用?这记录在哪里?这是预期的吗?
嗯,是的。允许方法调用其他方法。这几乎是方法的重点。 basename
返回一个新的Pathname
对象。那么,您认为它如何构建这个新的Pathname
对象?当然,它会调用self.class::new
(实际上是Class#new
),然后调用Pathname#initialize
。
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