2016年4月18日月曜日

Rubyの継承, include, prepend, extendの違い

Rubyの継承の仕方
Rubyはオブジェクト指向言語であるのだけれど、Javaの継承と考え方、方法が違うようなので、メタプログラミング Rubyを読みつつ調べてみた。

Module
RubyにはModuleという概念があり、これはメソッドがまとめられたものである。通常の継承のほかにこのModuleをincludeやprependすることにより、メソッドをクラスに取り込むことができるもよう。

早速試してみる。

通常の継承の例
[1] pry(main)> class MySubClass
[1] pry(main)*   def subClass
[1] pry(main)*     p "sub class"
[1] pry(main)*   end  
[1] pry(main)* end  
=> :subClass
[2] pry(main)> class MyClass < MySubClass
[2] pry(main)*   def myClass
[2] pry(main)*     p "my class"
[2] pry(main)*   end  
[2] pry(main)* end  
=> :myClass
[3] pry(main)> myclass = MyClass.new
=> #<MyClass:0x0000000d1eb628>
[4] pry(main)> myclass.subClass
"sub class"

=> "sub class"
ふむ、普通にメソッドを継承してる。


includeの例
[2] pry(main)> module MyModule
[2] pry(main)*   def myModuleMethod
[2] pry(main)*     p "myModuleMethod"
[2] pry(main)*   end  
[2] pry(main)* end  
=> :myModuleMethod
[3] pry(main)> class MyClass
[3] pry(main)*   include MyModule
[3] pry(main)*   def myMethod
[3] pry(main)*     p "myMethod"
[3] pry(main)*   end  
[3] pry(main)* end  
=> :myMethod
[4] pry(main)> myClass = MyClass.new
=> #<MyClass:0x0000000bf719e8>
[5] pry(main)> myClass.myModuleMethod
"myModuleMethod"

=> "myModuleMethod"
むむ、こちらも普通にメソッドを継承してる。

prependの例
[3] pry(main)> module MyModule
[3] pry(main)*   def myModuleMethod   
[3] pry(main)*     p "myModuleMethod"     
[3] pry(main)*   end     
[3] pry(main)* end  
=> :myModuleMethod
[4] pry(main)> class MyClass
[4] pry(main)*   prepend MyModule  
[4] pry(main)*   def myClass  
[4] pry(main)*     p "my class"    
[4] pry(main)*   end    
[4] pry(main)* end  
=> :myClass
[5] pry(main)> myclass = MyClass.new
=> #<MyClass:0x0000000cd80e08>
[6] pry(main)> myclass.myModuleMethod
"myModuleMethod"

=> "myModuleMethod"
はい、こちらも普通に継承

extendの例
[1] pry(main)> module MyModule
[1] pry(main)*   def myModuleMethod   
[1] pry(main)*     p "myModuleMethod"     
[1] pry(main)*   end     
[1] pry(main)* end  
=> :myModuleMethod
[2] pry(main)> class MyClass
[2] pry(main)*   extend MyModule  
[2] pry(main)*   def myClass  
[2] pry(main)*     p "my class"    
[2] pry(main)*   end    
[2] pry(main)* end  
=> :myClass
[3] pry(main)> myclass = MyClass.new
=> #<MyClass:0x0000000d374c88>
[4] pry(main)> myclass.myModuleMethod
NoMethodError: undefined method `myModuleMethod' for #<MyClass:0x0000000d374c88>
from (pry):13:in `<main>'
[5] pry(main)> MyClass.myModuleMethod
"myModuleMethod"

=> "myModuleMethod"
おお、extendはクラスメソッドになった!

じゃあinclude と pretend は一体何が違うのかというと、メソッドを呼び出していく順番に違いが出てくるらしい。

Includeの例 クラスの親階層にはいる。
2.1.5 :001 > module MyModule
2.1.5 :002?>     def my_class
2.1.5 :003?>         p "my_class in my module"
2.1.5 :004?>       end
2.1.5 :005?>   end
 => :my_class
2.1.5 :006 > class MyClass
2.1.5 :007?>     include MyModule
2.1.5 :008?>     def my_class
2.1.5 :009?>         p "my class"
2.1.5 :010?>       end
2.1.5 :011?>   end
 => :my_class
2.1.5 :012 > class SubMyClass < MyClass; end
 => nil
2.1.5 :013 > SubMyClass.ancestors
 => [SubMyClass, MyClass, MyModule, Object, Kernel, BasicObject]

2.1.5 :019 > sub_my_class = SubMyClass.new
 => #<SubMyClass:0x000000024ecff8>
2.1.5 :020 > sub_my_class.my_class
"my class"
 => "my class" 
Pretendの例 クラスの子階層の子になる。
2.1.5 :001 > module MyModule
2.1.5 :002?>     def my_class
2.1.5 :003?>         p "my_class in my module"
2.1.5 :004?>       end
2.1.5 :005?>   end
 => :my_class
2.1.5 :006 > class MyClass
2.1.5 :007?>     prepend MyModule
2.1.5 :008?>     def my_class
2.1.5 :009?>         p "my class"
2.1.5 :010?>       end
2.1.5 :011?>   end
 => :my_class
2.1.5 :012 > class SubMyClass < MyClass; end
 => nil
2.1.5 :013 > SubMyClass.ancestors
 => [SubMyClass, MyModule, MyClass, Object, Kernel, BasicObject]
2.1.5 :016 > sub_my_class = SubMyClass.new
 => #<SubMyClass:0x000000011fabc0>
2.1.5 :017 > sub_my_class.my_class
"my_class in my module"
 => "my_class in my module"

SubMyClassから呼び出した時に、PretendだとMyModuleが先にメソッドがあるかどうか走査され、IncludeだとMyClassが先に走査される。