向'self'发送消息会导致调用initialize方法吗?

时间:2017-01-03 09:15:36

标签: ruby oop

我有两个课程FooBar

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

所以打破它:

  1. 我实例化bar,这是Bar的一个实例,它继承自Foo。调用Bar的超类“initialize并发生”副作用“。到目前为止,这完全是预期的。
  2. 我在没有它的some_method上致电bar所以ruby上升到右边并在Foo
  3. 内找到它
  4. Ruby在some_method内跳转并找到一个向self发送消息的方法basename
  5. Ruby回到Foo的'initialize方法?...
  6. 第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
    

4 个答案:

答案 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));

如果它不是FooBar类所需的功能,则可以停止继承Pathname,并定义@pathname实例变量。

最后,您可能不希望像昨天提议的那样在initialize中自动创建目录:

获取file.txt的基本名称可以创建file目录。

答案 3 :(得分:2)

  

第4步让我完全惊讶。为什么要向self发送消息会导致initialize方法再次被调用?这记录在哪里?这是预期的吗?

嗯,是的。允许方法调用其他方法。这几乎是方法的重点。 basename返回一个新的Pathname对象。那么,您认为它如何构建这个新的Pathname对象?当然,它会调用self.class::new(实际上是Class#new),然后调用Pathname#initialize

This is what the implementation of Pathname#basename looks like in Rubinius's implementation of the Ruby standard libraries

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