今日、長期インターン中の開発で通知処理を Railsのconcern
で実装することになり、その時に「#include
,#extend
ってどっちがなんだったっけ?」となったので改めて調べてアウトプットすることにしました。
#include
と#extend
の違いは?
ズバリ、
-
あるモジュール内で定義されたメソッドを、別のクラスのインスタンスメソッドとして追加したい場合は
#include
を使う -
あるモジュール内で定義されたメソッドを、(クラスなどの)オブジェクト自身のメソッドとして追加したい場合は
#extend
を使う
そもそも#include
#extend
があると何が嬉しいのか?
Rubyでは、一つのクラスが複数のクラスを継承する「多重継承」が許されておらず、一つのクラスしか継承できません。その代わりに用いられるのが、特に回数制限なくメソッドや定数を追加できる#include
, #extend
です。ちなみに、#include
#extend
できる対象はモジュールのみです。
#include
公式リファレンス(#include)
include
は、別のモジュールで定義したメソッドをあたかも自身のクラスの中で定義したかのように取り込むことができます。
※以下、誤解させてしまいそうn表記があったためinclude
の説明に一部修正を加えました(@scivola ご指摘ありがとうございます🙇♀️)。
module TestModule
def test_method
puts "マヨネーズ"
end
end
class TestClass
include TestModule
def test_method2
# このモジュール内で定義したかのように呼べる
test_method
end
end
ということは、クラス内からinclude
すると、あるモジュールで定義したメソッドをそのクラスのインスタンスメソッドとして追加できるということになります。
module TestModule
def test_method
puts "マヨネーズ"
end
end
class TestClass
include TestModule
end
# インスタンスメソッドとして呼べる
test_instance = TestClass.new
test_instance.test_method # -> "マヨネーズ"
また第二に、include
はクラス内だけでなくモジュール内で呼ぶこともできます。
しかし、モジュールはインスタンス化できないので、クラス内でinclude
した時のようにインスタンスメソッドとして外部から呼ぶことはできません。
module TestModule
def test_method
puts "ようかん"
end
end
module TestModule2
include TestModule
def test_method2
# あたかも自身が定義したように呼べるのは同じ
test_method
end
end
# インスタンス化できないのでインスタンスメソッドとしては呼べない
TestModule.new.test_method # =>NoMethodError: undefined method `new' for TestModule2:Module
# モジュール自身からも当然呼べない
TestModule2.test_method # =>NoMethodError: undefined method `test_method' for TestModule2:Module
第三に、include
をすると、別のモジュール内で定義した定数もinclude
したクラスやモジュール内で使えるようになります。
実際、Railsアプリケーションでは、config/initializers/constants.rb
にてアプリケーションで使用する定数をモジュールで定義しておく実装が見られます。(Ruby on Railsで定数の指定)
----結論、include
は
- あるモジュールで定義したメソッドを、あたかも自身で定義したかのように使えるようにする
- クラス内で呼んだ場合は、インスタンスメソッドとして使えるようになる
- あるモジュールで定義した定数を、別のクラスやモジュールで使えるようにする
#extend
公式リファレンス(#extend)
extend
は、モジュールで定義したメソッドを、extend
を呼んだオブジェクト自身のメソッドとして追加します。include
は一律でインスタンスメソッドなので、そこが違う点です。
module TestModule
def test_method
puts "ドンキーコング"
end
end
class TestClass
extend TestModule
end
# インスタンスから呼ぶと NoMethodError になる
test_instance = TestClass.new
test_instance.test_method # => NoMethodError: undefined method `test_method' for #<TestClass:...>
# クラス自身から呼ぶと呼べる
TestClass.test_method # => "ドンキーコング"
extend
したモジュールはそれをextend
したオブジェクト自身だけが呼ぶことができます。なので、上記の場合はextend
を呼んだそのクラス自身だけが#test_method
を呼べるということです。
実質のところ、extend
はモジュールのメソッドをクラスメソッドとして取り入れる際に頻繁に使われます。
しかし、厳密にはextend
= クラスメソッド ではありません。extend
はオブジェクトであればクラス以外の何からでも呼べるのです。
module TestModule
def test_method
puts "ドンキーコング"
end
end
# オブジェクトなのでハッシュからも#extendが呼べる
test_hash = { partner: :dd_kong }
test_hash.extend(TestModule)
# そして使える
test_hash.test_method # => "ドンキーコング"
なので、「#include
はインスタンスメソッドで、#extend
はクラスメソッド!」と覚えるのも悪くはないのですが、厳密にいうと、
-
#include
- モジュール内で定義したメソッドをクラスのインスタンスメソッドとして使えるようにする
- モジュール内で定義したメソッドを別のモジュール内に追加する
- モジュール内で定義した定数を別のクラスやモジュールで使えるようにする
-
#extend
- それを呼んだオブジェクト自身に対してメソッドを追加できる
- クラスメソッドとして追加する場合によく使われる
という認識がより正確なようです!