初めてのRubyやり終えたのでメモがてら
継承 (親クラス と 子孫クラス)
Ruby のクラスはは 1つだけ親クラスを持つ
多言語とかだと継承って呼ぶ事が多いけど、Rubyも継承って言って良いのかがチョットわからない
同じメソッドがある場合、子孫の持つメソッドが優先される
class Super def foo p 'Super foo' end def bar p 'Super bar' end end class Sub < Super def foo p 'Sub foo' end end sub = Sub.new sub.foo # => "Sub foo" # 子孫クラスで上書きされてないメソッドは親クラスのものが直接呼び出される sub.bar # => "Super bar"
どのクラスが持っているメソッドか確認する
method(:メソッド名).owner
でシンボル形式で渡したメソッドを持っているクラスを確認することができる
p sub.method(:bar).owner # => Super p sub.method(:foo).owner # => Sub p sub.method(:class).owner # => Kernel
Module
Module インスタンス化できない機能の集まり (ModuleクラスはClassクラスの親)
Ruby ではクラスは1つしか継承できないが、Module はいくつでも継承?/読込みできる
Module をクラスに追加することを Mix-in と呼ぶらしい
include
include
で追加した Module は自身と親の間に挿入されるイメージ
Module に定義されたメソッドは、親クラスのメソッドより優先される
class Super def foo p 'Super foo' end def bar p 'Super bar' end end module Mod def foo p 'foo-foo' end def bar p 'bar-bar' end end class Sub < Super # Module を Mix-in include Mod def foo p 'Sub foo' end end sub = Sub.new # 自身の持つメソッドは includeした Module に上書きされない sub.foo # => "Sub foo" p sub.method(:foo).owner # => Sub # 親クラスの持つメソッドは Module が上書きする sub.bar # => "bar-bar" p sub.method(:bar).owner # => Mod
prepend
Ruby 2.0 で追加された機能
prepend
で追加した Module のメソッドは自身の持つメソッドより優先される
module Mod def foo p 'foo-foo' end end class Sub prepend Mod def foo p 'Sub foo' end end sub = Sub.new sub.foo # => "foo-foo" p sub.method(:foo).owner # => Mod
あくまで Mix-in されたクラスのメソッドより優先されるだけなので、親クラスに Module が prepend
されていても、子孫クラスのに同名のメソッドがあれば、自身(子孫クラス)のメソッドが呼び出される
module Mod def foo p 'foo-foo' end def bar p 'bar-bar' end end class Super prepend Mod def foo p 'Super foo' end end class Sub < Super def foo p 'Sub foo' end end sub = Sub.new sub.foo # => "Sub foo" # 親クラスに Mix-in されたメソッドも呼び出せる sub.bar # => "bar-bar"
継承順の確認
クラス名.ancestors
でModule, 親クラスの継承順を配列で確認することができる
module ModA end module ModB end module ModC end class Super include ModA end class Sub < Super include ModB prepend ModC end p Sub.ancestors # => [ModC, Sub, ModB, Super, ModA, Object, Kernel, BasicObject]
ancestors
で確認できる配列の先頭から順にメソッドが検索されていくイメージで良さそう。
Module が同じ名前のメソッドを持っている場合、後に追加されたものが優先される
include
でも prepend
でも同様に後に Mix-in された Module のメソッドが優先される
module ModA def hi p 'A' end end module ModB def hi p 'B' end end class X include ModA include ModB end X.new.hi # => "B" p X.ancestors # => [X, ModB, ModA, Object, Kernel, BasicObject] class Y include ModB include ModA end Y.new.hi # => "A" p Y.ancestors # => [Y, ModA, ModB, Object, Kernel, BasicObject] class A prepend ModA prepend ModB end A.new.hi # => "B" p A.ancestors # => [ModB, ModA, A, Object, Kernel, BasicObject] class B prepend ModB prepend ModA end B.new.hi # => "A" p B.ancestors # => [ModA, ModB, B, Object, Kernel, BasicObject]
特異メソッド ( singleton method )
特異メソッドは、クラスではなくインスタンスに直接生えているメソッド
def オブジェクト.メソッド名
の形で定義する
特異メソッドはクラスが持つメソッドより優先される
module Mod def foo p 'Mod Foo' end end class Foo prepend Mod def foo p 'foo' end end foo = Foo.new # 特異メソッドを定義 def foo.foo p 'FOO FOO' end foo.foo # => "FOO FOO" # 特異メソッドはインスタンスに生えているので `クラス名.ancestors` では確認できない p Foo.ancestors # => [Mod, Foo, Object, Kernel, BasicObject] # `インスタンス.singleton_class.ancestors` で自身のオブジェクトを含んだリストを見ることができる p foo.singleton_class.ancestors # => [#<Class:#<Foo:Object ID>>, Mod, Foo, Object, Kernel, BasicObject]
extend
extend
は特異クラスに Mix-in をする
extend
で追加した Module のメソッドはインスタンス自身の持っている特異メソッドは上書きしない
特異メソッド版の include
module ModA def foo; p 'Mod Foo' end def bar; p 'Mod Bar' end end module ModB def bar; p 'B' end end module ModC def bar; p 'C' end end class Foo prepend ModB include ModC def foo p 'foo' end def bar p 'bar' end end foo = Foo.new # Mod を特異クラスに Mix-in foo.extend Mod # 特異メソッドを追加 def foo.foo p 'FOO FOO' end # 自身の特異メソッドは extend した Module より優先される foo.foo # => "FOO FOO" # extend した Module のメソッドはクラスの持つインスタンスメソッドより優先される foo.bar # => "Mod Bar" # extend で追加した Module は自身のインスタンの次に追加されている p foo.singleton_class.ancestors # => [#<Class:#<Foo:Object ID>>, ModA, ModB, Foo, ModC, Object, Kernel, BasicObject]
まとめ
Ruby のメソッドの検索は下記の順で遡って検索する
- 特異メソッド
extend
でインスタンスに追加された Module のメソッド (特異メソッド)prepend
で追加された Module のメソッド- クラス自身の持つインスタンスメソッド
include
で追加された Module のメソッド- クラス内にメソッドが見つからない場合は親クラスを探索
- クラスの親を遡って一番上のObject クラスまで探索してメソッドが見つからなければ
BasicObject#method_missing
が呼び出されNoMethodError
が発生
メソッドの順番を確認したくなったら クラス名.ancestors
か インスタンス.singleton_class.ancestors
で継承順のリストを確認できる
どのクラスがメソッドを持っているか確認したい時は インスタンス.method(:メソッド名).owner
で確認することができる
JavaScript の prototype 継承っぽくてすんなり理解できた!
[参考]
- 作者: まつもとゆきひろ,David Flanagan,卜部昌平(監訳),長尾高弘
- 出版社/メーカー: オライリージャパン
- 発売日: 2009/01/26
- メディア: 大型本
- 購入: 21人 クリック: 356回
- この商品を含むブログ (129件) を見る