2017-01-03 12 views
2

私は2つのクラスFooBar持っている:あなたは "副作用" が2回起きているのを見ることができるように'self'にメッセージを送信すると、initializeメソッドが呼び出されますか?

Inside Foo init... 
Side effect happening... 
Inside some_method inside Foo... 
Inside Foo init... 
Side effect happening... 

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 

をこれが出力されます。 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をインスタンス化Fooから継承Barのインスタンスです。 Barのスーパークラス 'initializeが呼び出され、「副作用」が発生します。これまでのところこれは完全に期待されています。私はルビーが起動して、右に行くとRubyはsome_methodの内側にホップとbasename
  2. 呼ば selfにメッセージを送信しています方法を見つける Foo
  3. の内側にそれを見つけたので、それを持っていませんbarsome_method呼び出す
  4. RubyはバックFoo 's' は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 

答えて

4

なぜメッセージを送信すると、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> 
+2

は、それがむしろ 'Pathname'上に固定されているよりも、クラスを尊重しています。 – tadman

+0

私はそれが意図されているか分からない。 'rb_class_new_instance'のクラス引数として' rb_obj_class(s​​elf) 'を渡すだけの副作用でもあります。 IOW:「怠惰な実装方法」ほど「尊重」していないかもしれません。 –

2

basenameメソッドと関連があります。 documentationのソースコードに見られるように、basenameはクラスの別のオブジェクトをインスタンス化します。

2

Pathnameインスタンスメソッドは、通常、パス名のインスタンスを返します。これを行うには、現在のクラスでinitializeに電話する必要があります。

あなたがbasenameのソースコードを見れば:

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

それはあなたのFooBarクラスの所望の機能がない場合は、あなたがPathnameから継承停止、および@pathnameインスタンス変数を定義することができます。 fileディレクトリを作成することができfile.txtのベース名の取得

最後に、あなたはおそらく、あなたが昨日提案としてinitializeに自動的にディレクトリを作成する必要はありません。

2

ステップ4は驚いて私を完全にキャッチしています。自己にメッセージを送信すると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 
+0

どういう意味ですか?「self.class :: new'を呼び出しますか?何が起こっているのか私には意味があります。 'self'は私が内部にあるオブジェクトです。この場合、' self.new#=> Foo'は 'Foo.new'のようなものです。しかし、 'self.class :: new'というパターンは、メソッドが自分自身の新しいインスタンスを返すプロセスを開始するために使用しますか? – mbigras

+1

これは、同じクラスの新しいインスタンスを返す共通のパターンです。たとえば、[Rubiniusの 'pathname'ライブラリの実装](https://github.com/rubysl/rubysl-pathname/blob/2.0/lib/rubysl/pathname/pathname.rb#L876)を参照してください。他の回答者の2人が投稿したYARVよりも読みやすい。 –

関連する問題