メソッドを呼び出すとき、どんなことが起きているのか理解するには2つのことを意識しないといけないんです。
➀メソッド探索
➁実際に実行する
それぞれ見ていきましょう
➀メソッド探索
メソッド探索とは、その名前の通り、メソッドを探すことです。
それを考える際に、レシーバと継承チェーンという言葉が大事です。
例えば@books.each
というコードがあった時、@books がレシーバとなります。呼び出すメソッドが属しているオブジェクトのことをレシーバといいます。
継承チェーンとは親クラスをたどって最終的にBasicObjectクラスまでたどってメソッドがあるかを探すことです。
イメージとしてはこんな感じです
Object
↑superclass
Array
↑superclass
obj → Myclass
objにたいしてメソッドが呼ばれたときにこの矢印の順にメソッドを探していきます。この図にはないですが、最終的にBasicObjectまでメソッドを探していきます。Myclass.ancestors
とすると=> [Myclass, Array, Object, Kernel, BasicObject]
となります。
モジュールが入ってくると継承チェーンが変わっていきます
module Example
def ex_method
puts 'hoge!'
end
end
class C
include Example
end
class D < C; end
D.ancestors #=> [D, C, Example, Object, Kernel, BasicObject]
ExampleモジュールをincludeするとCクラスの上になるんですね。ではExampleをDクラスの直後に持っていきたいときにはどうしたらいいのでしょうか?そこで使われるのがprependメソッドです
module Example
def ex_method
puts 'hoge!'
end
end
class C
prepend Example
end
class D < C; end
D.ancestors #=>[D, Example, C, Object, Kernel, BasicObject]
prependメソッドとincludeメソッドは挙動が違うので注意しましょう。
➁実際に実行する
実際に実行するとき、意識しなければならないのはselfという言葉を理解しなければなりません。
selfとはオブジェクト自身のことを言います。言葉だけだと分からないと思いますので実例を見ていきましょう
class Exclass
def ex_self
@a = 5
ex_method
self
end
def ex_method
@a += 5
end
end
obj = Exclass.new
obj.ex_self => #<Exclass:0x0000021c41aa6658 @a=10>
ex_selfメソッドが呼び出されたとき、objがselfになります。
イメージするなら、「メソッドが適用されるオメー自身は誰なのか」という感じですかね
また、クラスやモジュール内(つまり、メソッド外で)で、selfの役割はクラスやモジュール自身のことなんです。
class Exclass
self #=> Exclass
end
クイズm
※以下のコードは書籍から引用しています
module Printable
def print
# ...
end
def prepare_cover
# ...
end
end
module Document
def print_to_screen
prepare_cover
format_for_screen
print
end
def format_for_screen
# ...
end
def print
# ...
end
end
class Book
include Document
include Printable
end
b = Book.new
b.print_to_screen
以上のコードを実行した時、print_to_screenメソッド内のprintメソッドは、
Printableモジュールのprintメソッドを呼び出しているのか、
それともDocumentモジュールのprintメソッドを呼び出しているのか、
それともKernelモジュールのprintメソッドを呼び出しているのか
これを考えていきましょう。それを考える際には、継承チェーンを考えていきましょう。
分かりましたか?
BasicObject
↑
Kernel
↑
Object
↑
Document
↑
Printable
↑
b → Book
まず、コード内で明示的にBookクラスの親クラスは指定されていないのでObjectクラスが親クラスとなります。そしてObjectクラスはKernelモジュールをインクルードして、BasicObjectクラスを継承します。
そして、BookクラスはDocumentモジュールとPrintableモジュールを順にインクルードしているので、上記のような継承チェーンとなります。
print_to_screenメソッドが呼び出されたとき、レシーバ(self)はbです。メソッドはクラスに存在するので、Bookクラスからメソッド探索が行われます。該当するメソッドが見つかった時点でメソッド探索は終了します。
見つかった場所はPrintableモジュール内のprintメソッドです。
では、Documentモジュール内のprintメソッドを呼び出したいとき、どんな風な処理を書けばいいでしょうか?
module Printable
def print
# ...
end
def prepare_cover
# ...
end
end
module Document
def print_to_screen
prepare_cover
format_for_screen
print
end
def format_for_screen
# ...
end
def print
# ...
end
end
class Book
include Printable # ←
include Document # ←
end
b = Book.new
b.print_to_screen
includeしているモジュールを変えれば、Documentモジュール内のprintメソッドを呼び出せます
補足
書籍の中でprivateメソッドを呼び出す際に、明示的にレシーバをつけてprivateメソッドは呼び出せないと書いてあるのですが、Ruby 2.7からは呼び出せるようになったみたいです。
以上です。前回と今回の記事が、メタプログラミングRuby2章のアウトプットでした
何か間違いがございましたら、ご教示いただけますと幸いです。
【参考文献】