pandamanian’s diary

rubyとrailsについてアウトプットします!

『Ruby』includeとprepend

お疲れ様です!

今回はincludeとprependの違いについてアウトプットです😊

配列に対して使用出来るshuffleというメソッドをオーバーライドする事によってincludeメソッドとprependメソッドの挙動の違いを確認して行きます!

includeとは

実際にコードを書いて動作を確認しましょう。

[1] pry(main)> module Calc
[1] pry(main)*   def calc
[1] pry(main)*     p "計算機能"
[1] pry(main)*   end  
[1] pry(main)* end  
=> :calc

これで"計算機能"という文字列を出力する事が可能なmoduleを定義しました。
次にaddクラスを作ってこのCalcモジュールをincludeします!

[2] pry(main)> class Add
[2] pry(main)*   include Calc
[2] pry(main)* end  
=> Add

ここから取り込んだCalcモジュールの機能を使ってみます!

[3] pry(main)> a = Add.new
=> #<Add:0x00007fe1482cbdf8>
[4] pry(main)> a.calc
"計算機能"
=> "計算機能"

ここではaという変数にAdd.newでAddインスタンスを作成して代入しています!
aというインスタンスに対してcalcというメソッドを使用して"計算機能"と言う文字列をさせています。
つまりクラスの中でinclude + module名とする事でインスタンスに対してmodule内の機能を取り込む事が出来るんですね😊

ちなみにクラスメソッドとして呼び出す事は出来ません!

[5] pry(main)> Add.calc
NoMethodError: undefined method `calc' for Add:Class
from (pry):11:in `__pry__'

Addクラスに対してcalcメソッドはありません💢ってなります。。。

ここでは詳しくは話しませんがクラスメソッドとして使いたい場合はextendを使用する事で解決します。
上の構文のincludeをextendに書き加える感じですね😊

まとめるとincludeメソッドとはモジュールの機能をクラスに取り込んでそのクラスのインスタンスに対してその機能を使用出来る様にするメソッドですね!
つまり
モジュール(Calc)の 機能(calc)を クラス(Add)に取り込んで
そのクラスのインスタンス(a)に対して使用出来る様にしています!

prependとは

さてさて今度は上の構文をそのままprependに置き換えてみましょう!

[1] pry(main)> module Calc
[1] pry(main)*   def calc  
[1] pry(main)*     p "計算機能"    
[1] pry(main)*   end  
[1] pry(main)* end  
=> :calc
[2] pry(main)> class Add
[2] pry(main)*   prepend Calc
[2] pry(main)* end  
=> Add
[3] pry(main)> a = Add.new
=> #<Add:0x00007fae4b3a9de0>
[4] pry(main)> a.calc
"計算機能"
=> "計算機能"

何も変化はありません😭

じゃあ何が違うのよ🤔って話ですが
結論から言うとクラスを先に参照するかモジュールを先に参照するのかって事です😊
ここでは実際にArrayクラスで使えるshuffleメソッドをオーバーライドする事でincludeとprependの違いを確認してみましょう!

まずprependの効果を確認します。

[1] pry(main)> a = (1..10).to_a
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[2] pry(main)> b = (1..4).to_a
=> [1, 2, 3, 4]

[4] pry(main)> module Shuffle
[4] pry(main)*   def shuffle
[4] pry(main)*     self.length < 5 ? reverse : super
[4] pry(main)*   end  
[4] pry(main)* end  
=> :shuffle

上の構文ではShuffleモジュールを定義してその中でshuffleメソッドを定義しています。
インスタンスのlengthが5未満ならreverseメソッドを呼び出して5以上ならsuper(元々定義されているshuffleメソッド)を呼び出すメソッドですね。

[5] pry(main)> class Array
[5] pry(main)*   prepend Shuffle
[5] pry(main)* end  
=> Array

上の構文ではArrayクラスにShuffleモジュールをPrependしたのでArrayクラスから作られるインスタンスでmoduleで定義されたshuffleメソッドが使用出来ます!


では早速・・・

[6] pry(main)> arr = Array.new(a)
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[7] pry(main)> arr2 = Array.new(b)
=> [1, 2, 3, 4]

[10] pry(main)> arr.shuffle
=> [4, 10, 1, 9, 5, 7, 3, 2, 8, 6]
[11] pry(main)> arr2.shuffle
=> [4, 3, 2, 1]

出来ました!
arrインスタンスの配列のlengthは5以上(false)なのでArrayクラスに元々備わっているshuffleメソッドが呼び出されました。
arr2インスタンスの入れるの長さは5未満(true)なのでmodule内で定義したreverseメソッドが呼び出されました。

これはArrayクラスに元々備わっているshuffleメソッドではなくmoduleで定義されたshuffleメソッドが先に呼び出された事を意味します。

呼び出される順番を知ろう

どの順番で呼び出されるかを知る為に便利なメソッドがあります。
ancestorsメソッドを使用すると参照する順番を知る事が出来ます!

[14] pry(main)> Array.ancestors
=> [Shuffle,
 Array,
 Enumerable,
 Object,
 PP::ObjectMixin,
 Kernel,
 BasicObject]

先にShuffleモジュールが参照されている事が分かります。 この様にprependではArrayクラスよりも先にmoduleで定義されたshuffleが先に参照される事が分かりました。

prependからincludeに変化させると参照先の順番が変化します!

[5] pry(main)> class Array
[5] pry(main)*   include Shuffle
[5] pry(main)* end  
=> Array

[8] pry(main)> arr = Array.new(a)
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[9] pry(main)> arr2 = Array.new(b)
=> [1, 2, 3, 4]
[10] pry(main)> arr.shuffle
=> [2, 9, 4, 7, 5, 8, 3, 1, 10, 6]
[11] pry(main)> arr2.shuffle
=> [2, 1, 3, 4]

[12] pry(main)> Array.ancestors
=> [Array,
 Shuffle,
 Enumerable,
 Object,
 PP::ObjectMixin,
 Kernel,
 BasicObject]

参照先の先頭がArrayクラスなのでArrayクラスに元々組み込まれているshuffleメソッドが先に参照されたのでarrもarr2も普通にシャッフルされました!

もう一度結論から言うと
includeはクラスを先に参照する。
prependはモジュールを先に参照にする。
って事です😊

少しでもincludeとprependの違いを模索している方の力になれれば幸いです😇