【Ruby】include, prepend, extendの違いと使い分け

## はじめに

クラスにモジュールを取り込む方法として、`include`, `prepend`, `extend`の3つがあります。

この記事では3つの方法の違いと使い分けについてまとめていきたいと思います。

なお、この記事におけるバージョンは**Ruby 3.3**です。

## そもそもモジュールとは？

モジュールは関連するメソッドや定数をグループ化したものです。

クラスと似ていますが、以下の2点で異なります。

- モジュールはインスタンス化できない
- モジュールは継承できない

複数のクラスに共通の機能を提供したり、名前空間を分割してメソッド名の衝突を避けたりしたい場合に用いられます。

## `include`の使い方と特徴

`include`はモジュールのメソッドをクラスへ取り込むために使われます。

### メソッドの探索順序

`include`を使うとモジュールがメソッドの探索順序に組み込まれます。

その結果、次の順序でメソッドが探索されます。

1. クラス自身に定義されているメソッド
2. `include`されたモジュールのメソッド
3. スーパークラスのメソッド

メソッドの探索順序において、`include`で取り込んだモジュールはクラスの真上に挿入されるイメージとなります。

クラスとスーパークラスの間に挿入された状態です。

具体的なコードで確認していきます。

### `include`の具体例

以下の例では`Greet`モジュールが`Child`クラスに取り込まれています。

そのため、`Parent`クラスの`hello`メソッドではなく、`Greet`モジュールの`hello`メソッドが呼び出されているのです。

```ruby
module Greet
  def hello
    "Hello from Greet module"
  end
end

class Parent
  def hello
    "Hello from Parent class"
  end
end

class Child < Parent
  include Greet
end

child = Child.new
child.hello
#=> Hello from Greet module
```

`ancestors`メソッドで探索順序を確認してみます。

```ruby
Child.ancestors　　#　Greet モジュールがメソッドの探索順序に追加されている
#=> [Child, Greet, Parent, Object, PP::ObjectMixin, Kernel, BasicObject]
```

たしかに`Child`クラスと`Parent`クラスの間に`Greet`モジュールが探索されることを確認できました。

また1つのクラスに複数のモジュールを`include`した場合の挙動も確認します。

次の例は上記に加えてさらに`Hoge`モジュールも`include`する例です。

```ruby
module Greet
  def hello
    "Hello from Greet module"
  end
end

module Hoge
  def hello
    "Hello from Hoge module"
  end
end

class Parent
  def hello
    "Hello from Parent class"
  end
end

class Child < Parent
  include Greet
  include Hoge
end

Child.ancestors
#=> [Child, Hoge, Greet, Parent, Object, PP::ObjectMixin, Kernel, BasicObject]
```

`Hoge`モジュールが`Child`クラスと`Greet`モジュールの間に挿入されています。

`Hoge`モジュールが`Child`クラスの真上に挿入されることで、元々クラスの真上に存在していた`Greet`モジュールは押し上げられてしまうのです。

このように、`include`で取り込まれたモジュールはクラスの真上に配置されます。

:::note info
`include`したモジュールは親クラスよりも先に探索される
モジュールAとBを`include`したら「クラス → B → A → 親クラス」の順で探索する
:::

## `prepend`の使い方と特徴

`prepend`も`include`と同じく、モジュールをクラスへ取り込むために使います。

### メソッドの探索順序

`prepend`を使った時のメソッド探索の順序は次のようになります。

1. `prepend`されたモジュールのメソッド
2. クラス自身に定義されているメソッド
3. スーパークラスのメソッド

探索順序において、`include`はクラスの真上にモジュールを挿入するのに対し、`prepend`はクラスの下にモジュールを挿入します。

つまり、クラス自身よりも優先的にモジュールが探索されます。

そのためクラスに定義されているメソッドの上書きが可能です。

### `prepend`の具体例

`include`で使用したサンプルコードを少し書き換えます。

`Child`クラスへ`Greet`モジュールを`prepend`で取り込み、また`Child`クラス自身にも`hello`メソッドを定義しました。

```ruby
module Greet
  def hello
    "Hello from Greet module"
  end
end

class Parent
  def hello
    "Hello from Parent class"
  end
end

class Child < Parent
  prepend Greet

  def hello
    "Hello from Child class"
  end
end

child = Child.new
child.hello
#=> "Hello from Greet module"
```

`Child`クラス自身に定義されている`hello`メソッドではなく、`Greet`モジュールに定義された`hello`メソッドが実行されました。

こちらも`ancestors`メソッドで探索順序を確認してみます。

```ruby
Child.ancestors　　# Greet モジュールが Child クラスの前に探索されている
#=> [Greet, Child, Parent, Object, JSON::Ext::Generator::GeneratorMethods::Object, PP::ObjectMixin, Kernel, BasicObject]
```

今度は`Child`クラスの前に`Greet`モジュールが挿入されていますね。

おなじ流れで複数のモジュールを`prepend`した場合の挙動も確認します。

```ruby
module Greet
  def hello
    "Hello from Greet module"
  end
end

module Hoge
  def hello
    "Hello from Hoge module"
  end
end

class Parent
  def hello
    "Hello from Parent class"
  end
end

class Child < Parent
  prepend Greet
  prepend Hoge

  def hello
    "Hello from Child class"
  end
end

Child.ancestors
#=> [Hoge, Greet, Child, Parent, Object, JSON::Ext::Generator::GeneratorMethods::Object, PP::ObjectMixin, Kernel, BasicObject]
```

`Hoge`モジュールが`Greet`モジュールのさらに下へ挿入されています。

複数のモジュールを`prepend`した場合は一番下に書いたモジュールが優先されるのです。

:::note info
`prepend`したモジュールはクラス自身よりも先に探索される
モジュールAとBを`prepend`したら「B → A → クラス → 親クラス」の順で探索する
:::

## `extend`の使い方と特徴

`extend`はモジュールのメソッドを特定のオブジェクトに対してのみ追加します。

`include`や`prepend`と異なり、クラス全体にはモジュールが追加されません。

他の2つとは性質が違うため、注意が必要です。

### メソッドの探索順序

くりかえしになりますが、`extend`はクラス全体にはモジュールが追加されません。

そのため探索順序の概念は適用されないです。

ではどのように動作するのか、具体的なコードで見ていきたいと思います。

#### `extend`の具体例

`extend`を使ってクラスにモジュールを取り込んでも、そのクラスから生成されたインスタンスはモジュールに定義されたメソッドを使えません。

```ruby
module Greet
  def hello
    "Hello from Greet module"
  end
end

class Child
  extend Greet
end

child = Child.new
child.hello
#=> undefined method `hello' for an instance of Child (NoMethodError)

Child.ancestors  # 継承チェーンに Greet は追加されていない
#=> [Child, Object, JSON::Ext::Generator::GeneratorMethods::Object, PP::ObjectMixin, Kernel, BasicObject]
```

`extend`は特定のオブジェクトのみにメソッドを追加するからです。

上記の例だと、`Child`クラスに`Greet`モジュールの`hello`メソッドが追加されたわけではありません。

`Child`オブジェクトにのみモジュールが追加されたことになっています（Rubyではクラス自身もオブジェクト）

次のように`Child`クラスのクラスメソッドとして`hello`を実行すると、`Greet`モジュールの`hello`が呼び出されます。
```ruby
module Greet
  def hello
    "Hello from Greet module"
  end
end

class Child
  extend Greet
end

Child.hello
#=> "Hello from Greet module"
```

`Child`クラスのインスタンスに`Greet`モジュールを追加したい場合、次のように`Child`オブジェクトに個別に`extend`する必要があります。

```ruby
module Greet
  def hello
    "Hello from Greet module"
  end
end

class Child
end

child1 = Child.new
child1.extend Greet  # child オブジェクトに個別で extend する
child1.hello
#=> "Hello from Greet module"

child2 = Child.new
child2.hello  # child2 には extend していないため hello は使えない
#=> undefined method `hello' for an instance of Child (NoMethodError)
```

:::note info
`extend`したモジュールはそもそも継承チェーンに追加されない
`extend`したオブジェクト自身のメソッドとしてのみ使える
クラスにモジュールを`extend`したらクラスメソッドとしてのみ使える
:::

## `include`, `prepend`, `extend`の使い分け

この3つの使い分けは次のようになるかなと思います。

- `include`: モジュールのメソッドをクラスのインスタンスメソッドとして追加したい
- `prepend`: クラスのメソッドよりもモジュールのメソッドを優先したい or 上書きしたい
- `extend`: 特定のオブジェクトにのみメソッドを追加したい（主にクラスメソッドを追加）

知っておくと業務ではもちろん、OSSのコードを読む際に役立つなと感じました。

## 参考資料

https://www.amazon.co.jp/dp/4873117437

https://docs.ruby-lang.org/ja/latest/method/Module/i/include.html

https://docs.ruby-lang.org/ja/latest/method/Module/i/prepend.html

https://docs.ruby-lang.org/ja/latest/method/Object/i/extend.html