読者です 読者をやめる 読者になる 読者になる

【Ruby】includeとprependとextendの違いと用途

スポンサーリンク

Ruby の include, prepend, extend の違いと用途についてまとめました。

Module#include

指定したモジュールをincludeすることをMix-inといい、クラスがモジュールをインクルードすると、モジュールのインスタンスメソッドを手に入れます。クラスをインクルードすることはできませんがモジュールにインクルードすることはできます。

module MyModule
  def my_method; "#{__method__} called!!"; end
end

class MyClass
  include MyModule
end

MyClass.new.my_method  #=> "my_method called!!"
MyClass.instance_methods.grep(/my_method/)  #=> [:my_method]
MyClass.my_method  #=> NoMethodError: undefined method `my_method' for MyClass:Class

インクルードされたモジュールにクラスメソッドが存在しても、インクルードしたクラスからは呼び出せません。

module MyModule
  def self.my_method; "#{__method__} called!!"; end
end

class MyClass
  include MyModule
end

MyClass.my_method  #=> NoMethodError: undefined method `my_method' for MyClass:Class
MyClass.methods.grep(/my_method/)  #=> []

モジュールをインクルードすると、継承チェーンはインクルードするクラスの真上に入ります。メソッドの探索はスーパークラスよりもインクルードされたモジュールのほうが先に行われます。

MyClass.ancestors
=> [MyClass, MyModule, Object, Kernel, BasicObject]

クラス拡張

moduleincludeしてクラスメソッドを定義するには、特異クラスにモジュールをインクルードします。インクルードしたモジュールは特異クラスのインスタンスメソッドになり、MyClass のクラスメソッドとなります。

module MyModule
  def my_method; "#{__method__} called!!"; end
end

class MyClass
  class << self
    include MyModule
  end
end

MyClass.my_method  #=> "my_method called!!"
MyClass.singleton_methods  #=> [:my_method]

モジュール拡張

クラス拡張をオブジェクトに適用することをモジュール拡張と呼びます。オブジェクトの特異クラスにモジュールをインクルードします。

module MyModule
  def my_method; "#{__method__} called!!"; end
end

obj = Object.new
class << obj
  include MyModule
end

obj.my_method  #=> "my_method called!!"
obj.singleton_methods  #=> [:my_method]

instance method Module#include (Ruby 2.4.0)

Module#prepend

指定したモジュールをprependすると、includeと同様にモジュールのインスタンスメソッドを手に入れます。継承チェーンはプリペンドしたクラスの手前に入ります。そのため、結果としてプリペンドしたクラスで定義されているメソッドはオーバーライドされます。self のメソッドを使用したい場合はsuperで呼び出すことが可能です。オープンクラスで String の reverse メソッドをオーバーライドしてみました。

module MyModule
  def reverse
    self.size > 5 ? self.upcase : super
  end
end

class String
  prepend MyModule
end

"programing".reverse  #=> "PROGRAMING"
"ruby".reverse  #=> "ybur"

継承チェーンは以下のの通りです。

String.ancestors
=> [MyModule, String, Comparable, Object, Kernel, BasicObject]

instance method Module#prepend (Ruby 2.4.0)

Object#extend

指定したモジュールをextendすると、モジュールのインスタンスメソッドを特異メソッドとして追加します。includeはクラスのインスタンスにメソッドを追加しますが、extendはレシーバの特異クラスにモジュールをインクルードしてクラスメソッドとして使用できます。つまり、特異クラスに対するincludeであるクラス拡張 (モジュール拡張) と同じです。

module MyModule
  def my_method; "#{__method__} called!!"; end
end

class MyClass
  extend MyModule
end

MyClass.new.my_method  #=> NoMethodError: undefined method `my_method' for #<MyClass:0x007fd32104f280>
MyClass.singleton_methods  #=> [:my_method]
MyClass.my_method  #=> "my_method called!!"
obj = Object.new
obj.extend MyModule
obj.my_method  #=> "my_method called!!"
obj.singleton_methods  #=> [:my_method]

includeと同様に extend されたモジュールにクラスメソッドが存在しても、extend したクラスからは呼び出せません。

module MyModule
  def self.my_method; "#{__method__} called!!"; end
end

class MyClass
  extend MyModule
end

MyClass.my_method  #=> NoMethodError: undefined method `my_method' for MyClass:Class
MyClass.methods.grep(/my_method/)  #=> []

instance method Object#extend (Ruby 2.4.0)