【问题标题】:Sending a message to 'self' causes initialize method to be called?向“self”发送消息会导致调用初始化方法?
【发布时间】:2017-01-03 09:15:36
【问题描述】:

我有两个班级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 的一个实例,它继承自FooBar's superclass' initialize 被调用并且“副作用”发生。到目前为止,这完全是意料之中的。
  2. 我在bar 上给some_method 打电话,谁没有它,所以ruby 向右移动,在Foo 内部找到它
  3. Ruby 跳转到 some_method 内部并找到一个向 self 发送消息的方法,称为 basename
  4. Ruby 回到Foo's'initialize 方法?...

第 4 步完全出乎我的意料。为什么向self 发送消息会导致再次调用初始化方法?这在任何地方都有记录吗?这是预期的吗?

可以控制吗?或者有条件地检查我是否在初始化方法中,因为我实际上是在实例化一个类而不是随机登陆那里?例如:

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

【问题讨论】:

    标签: ruby oop


    【解决方案1】:

    为什么向 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

    您可以轻松验证这一点:

    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>
    

    【讨论】:

    • 有趣的是它尊重类而不是固定在Pathname
    • 我什至不确定这是有意的。这也可能是仅将rb_obj_class(self) 作为类参数传递给rb_class_new_instance 的副作用。 IOW:与其说是“尊重”不如说是“懒惰的实施方式”。
    【解决方案2】:

    正如你所说,它与basename 方法有关。从documentation的源码可以看出,basename实例化了该类的另一个对象。

    【讨论】:

      【解决方案3】:

      Pathname 实例方法通常返回 Pathname 实例。为此,他们需要在当前类上调用initialize

      如果你看basename的源代码:

       return rb_class_new_instance(1, &str, rb_obj_class(self));
      

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

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

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

      【讨论】:

        【解决方案4】:

        第 4 步完全出乎我的意料。为什么向 self 发送消息会导致再次调用初始化方法?这在任何地方都有记录吗?这是预期的吗?

        嗯,是的。允许方法调用其他方法。这几乎就是方法的全部意义所在。 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
        

        【讨论】:

        • “它调用self.class::new”是什么意思?这对我来说很有意义。 self 是我在里面的对象,所以在这种情况下self.new #=&gt; Foo 就像Foo.new。但是self.class::new 是方法用来启动返回自身新实例的过程的模式吗?
        • 这是返回同一类的新实例的常见模式,是的。例如,请参阅Rubinius's implementation of the pathname library,我发现它比其他几个回答者发布的 YARV 更具可读性。
        猜你喜欢
        • 2017-02-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多