今更聞けない! Ruby の継承と mixin の概念を継承リストから学ぶ

[ブログ記事](http://secret-garden.hatenablog.com/entry/2017/09/11/214756)からの転載です。  

Ruby を学ぶ上で継承と mixin の概念を理解することはとても重要である。  
しかし、このあたりの仕組みを学ぼうとすると、`include` や `prepend`、特異クラスや特異メソッドなどという様々な機能を理解する必要がありとても複雑である。  
そこで本記事は『継承リスト』という観点から継承を mixin を学んでみたいと思う。  
また、この記事は Ruby 2.4 時点での仕様になる。  

## 継承と mixin

まず、はじめに Ruby での継承と mixin の仕方をみてみよう。  
Ruby では継承で既存のクラスを、mixin でモジュールをそれぞれクラスに取り込むことが出来る

#### 継承

```ruby
class Super
	def super_method
		:super_method
	end
end

# Super クラスの機能（メソッド）を Sub クラスで継承（取り込む）する
class Sub < Super
end

# Super クラスのメソッドが利用できる
p Sub.new.super_method
# => :super_method
```


#### mixin

```ruby
module Mod
	def mod_method
		:mod_method
	end
end

class X
	# Mod モジュールの機能（メソッド）を X クラスで取り込む
	include Mod
end

# Mod モジュールのメソッドが利用できる
p X.new.mod_method
# => :mod_method
```

他にも `#extend` や `#prepend` といったメソッドでモジュールを mixin することが出来る。  
継承も mixin も『外部の機能をクラスに取り込む』という意味では共通だが、

* 1つのクラスしか継承する事が出来ない(継承してるクラスを継承するのは可)
* 複数のモジュールを mixin することが出来る

という点で少し異なる。  
また、継承と mixin は同時に利用することも可能である。  

```ruby
class Super
	def super_method
		:super_method
	end
end

module Mod
	def mod_method
		:mod_method
	end
end

class X < Super
	include Mod
end

x = X.new
p x.super_method
# => :super_method

p x.mod_method
# => :mod_method
```


## `Module#ancestors`

ここまで読んで『継承と mixin が複数あってややこしい…』と思うかもしれない。  
そこでまずは継承関係を『継承リスト』という形で視覚化してみてみよう。  
継承リストは `Module#ancestors` メソッドで取得する事が出来る。  
`Module#ancestors` はクラスオブジェクトをレシーバとして呼び出すことができ『クラス、モジュールのスーパークラスとインクルードしているモジュール を優先順位順に配列に格納して返す』というメソッドである。  

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

説明を聞いてもよくわからないと思うので実際に試してみよう。  
例えば、`Integer` であれば

```ruby
p Integer.ancestors
# => [Integer, Numeric, Comparable, Object, Kernel, BasicObject]
```

となり、`String` であれば

```ruby
p String.ancestors
# => [String, Comparable, Object, Kernel, BasicObject]
```

となる。  
これは単純に

```
BasicObject
↑
Kernel
↑
Object
↑
Comparable
↑
Numeric
↑
Integer
```

というような『継承関係にある』と考えれば理解しやすいだろう（厳密に『継承』と言ってしまうと正しくはないが…。  
また継承リストには『レシーバのクラス』も含まれている。  
ちなみにユーザが定義したクラスは暗黙的に `Object` を継承することになる。  

```ruby
class X
end

p X.ancestors
# => [X, Object, Kernel, BasicObject]
```

更に継承を行った場合は `Object` と定義したクラスの間に継承したクラスが挿入される。

```ruby
class Super
end

class Sub < Super
end

p Sub.ancestors
# => [Sub, Super, Object, Kernel, BasicObject]
```

このように `Module#ancestors` を使うことでそのクラスの『継承リスト』を確認する事が出来る。  


## メソッドを呼び出す優先順位

ここで少し継承と mixin から離れて『メソッドの探査』について考えてみよう。  
と、言っても実はそんなに難しくない。メソッドの探査は  
『継承リストの先頭から走査して最初に定義されているクラスのメソッドを実行する』  
というような形になる。  

```ruby
class Super
	def mami
		"mami"
	end

	def homu
		"homu"
	end
end

class Sub < Super
	# Super クラスのメソッドを上書きする
	def homu
		"homuhomu"
	end
end

# Super よりも Sub のほうが優先順位が高い
p Sub.ancestors
# => [Sub, Super, Object, Kernel, BasicObject]

sub = Sub.new

# Sub では定義されていないので次の Super のメソッドが呼び出される
p sub.mami
# => "mami"

# Sub で定義されているので Sub のメソッドが呼び出される
p sub.homu
# => "homuhomu"

# Sub でも Super でも定義されていないので、Kernel で定義されているメソッドを呼び出す
p sub.class
# => Sub
```

ここで重要なのが『継承リストがそのままメソッド探査の優先順位になる』という点である。  
逆にいえば『メソッド探査は継承リストに依存してる』ともいえる。  
このあたりは重要なので覚えておくとよい。  
余談だが、`Method#owner` でメソッドが定義されているクラス/モジュールを確認する事が出来る。

```ruby
sub = Sub.new
p sub.method(:mami).owner
# => Super

p sub.method(:homu).owner
# => Sub

p sub.method(:class).owner
# => Kernel
```

## mixin したクラスの `#acestors` をみてみる

これまでの流れで、

* 継承リストは `Module#ancestors` で取得する事が出来る
* 継承リストには自身も含まれている
* メソッド探査の優先順位は継承リストに依存する
* 継承を行うと継承したクラスが継承リストに挿入される

と、いうことがわかったと思う。  
では、`#include` でモジュールを mixin をするとどうなるか。  
実際に試してみよう。  

```ruby
module Mod
end

class X
	include Mod
end

# Mod がいる！！
p X.ancestors
# => [X, Mod, Object, Kernel, BasicObject]
```

あれ、 mixin しても継承リストに追加されてる！？  
そう、mixin も実は単に継承リストに追加されるだけなのだ。  
ちなみに先程から継承リストに存在している `Kernel` も実はクラスではなくて『`Object` に mixin されているモジュール』なのである。  

```ruby
p Kernel.class
# => Module
```


## 継承と mixin を両方使ってみると

では、継承と mixin の両方を行うとどうなるのか試してみよう。  

```ruby
class Super
end

module Mod
end

class X < Super
	include Mod
end

p X.ancestors
# => [X, Mod, Super, Object, Kernel, BasicObject]
```

上のコードの実行結果を見てもらうと `Mod` モジュールとと `Super` クラスが継承リストに追加されていることがわかる。  
また、継承リストから見てわかる通り、継承と mixin を行った場合は mixin のほうが前に挿入されていることがわかる。  
これにより『mixin を行ったモジュールのメソッドを優先して』呼び出されることになる。

```ruby
class Super
	def homu
		"Super#homu"
	end
end

module Mod
	def homu
		"Mod#homu"
	end
end

class X < Super
	include Mod
end

p X.new.homu
# = > "Mod#homu"
```

ちなみに複数のモジュールを mixin した場合は『あとから mixin したモジュール』の優先順位が高くなる。  

```ruby
module Mod1
end

module Mod2
end

class X
	include Mod1
	include Mod2
end

p X.ancestors
# = > [X, Mod2, Mod1, Object, Kernel, BasicObject]
```

このように継承も mixin も『継承リストに挿入されているだけ』に過ぎないのである。  

## `prepend` する

ところで Ruby 2.0 で `Module#prepend` というメソッドが追加された。  
これは `#include` と同様にモジュールを mixin する機能であるが、『優先順位が mixin されたクラスよりも高い』という特性がある。

```ruby
module Mod
	def homu
		"Mod#homu > #{super()}"
	end
end

class X
	prepend Mod

	def homu
		"X#homu"
	end
end

# X#homu ではなくて Mod#homu が呼ばれる
p X.new.homu
# "Mod#homu > X#homu"
```

このように `#prepend` したモジュールのメソッドが自身のクラスのメソッドよりも優先して呼ばれる。  
では、これも継承リストを見てみよう。  

```ruby
module Include
end

module Prepend
end

class X
	include Include
	prepend Prepend
end

p X.ancestors
# => [Prepend, X, Include, Object, Kernel, BasicObject]
```

`#include` したモジュールと比較すると `#prepend` したモジュールが『自身のクラスよりも前に』継承リストに挿入されている事がわかる。  
このようにして継承リストを確認することで『自身よりも `#prepend` したモジュールが優先して呼ばれる理由』も納得すると思う。  
こうしてみると難しそうな `#prepend` も単純に『継承リストに挿入する位置が違うだけ』なのがわかると思う。  
まとめると

* `#include` はクラスよりも前に継承リストに挿入される
* `#prepend` はクラスよりも後に継承リストに挿入される

という形になる。  


## `singleton_class.ancestors` をみている

ここまで来たら特異メソッドについても考えみよう。  
Ruby ではクラスだけではなくて『インスタンスに対しても』メソッドを定義することができる。  
また、そのメソッドはクラスで定義されているメソッドよりも優先して呼ばれる。  

```ruby
class X
	def homu
		"X#homu"
	end
end

x = X.new

def x.homu
	"x.homu"
end

p x.homu
# => "x.homu"
```

このように『インスタンスに対して定義されるメソッド』のことを『特異メソッド』と呼ぶ。  
また、特異メソッドは『特異クラス』という特殊なクラスに定義される。  
この特異クラスは『各オブジェクトが必ず1つ持っている』クラスになる。  

```ruby
class X
end

# X クラスの特異クラス
p X.singleton_class
# => #<Class:X>

x = X.new

# x インスタンスの特異クラス
p x.singleton_class
# => #<Class:#<X:0x00000001275e10>>
```

ちょっと複雑になってきたので特異メソッドと特異クラスはこのあたりにしてメソッド探査の話をしよう。  
メソッド探査が継承リストに依存しているのはこれまで話したとおりである。  
と、いうことで特異クラスの継承リストを確認してみよう。  

```ruby
class X
end

x = X.new

p x.singleton_class
# => #<Class:#<X:0x00000001828790>>
p x.singleton_class.ancestors
# => [#<Class:#<X:0x00000001828790>>, X, Object, Kernel, BasicObject]
```

上のコードを見てもらうと『特異クラスが自身の継承リストに追加されている』事がわかる。  
なのでメソッド探査に依存する継承リストというのは正確にいえば『自身の特異クラスの継承リスト』ということになる。    
これにより『クラスで定義されたメソッド』よりも『特異クラスで定義されたメソッド＝特異メソッド』が優先されるのである。  


## `#extend` してみる

最後に `#extend` について説明しておく。  
これも `#include` や `#prepend` と同様にモジュールを mixin する機能であるが『特異クラスに mixin する』という点が他のメソッドと異なる。  

```ruby
module Mod
	def homu
		"Mod#homu"
	end
end

class X
end

x = X.new

# Mod のメソッドが x オブジェクトに定義される
x.extend Mod

p x.homu
# => "Mod#homu"

# 特異クラスの継承リストに extend したモジュールが追加されている
p x.singleton_class.ancestors
# => [#<Class:#<X:0x000000017a51b0>>, Mod, X, Object, Kernel, BasicObject]
```

このように `#extend` は『特異クラスの継承リスト』に追加される。  
逆にいえば

```ruby
x.extend Mod
```

は

```ruby
x.singleton_class.include Mod
```

と同等ともいえるだろう。  


## おまけ：特異メソッドよりも優先するメソッドを定義する

おまけ。  
特異クラスに対して `#prepend` することで特異メソッドよりも優先されるメソッドを定義することが出来る。  

```ruby
module Mod
	def homu
		"Mod#homu"
	end
end

class X
end

x = X.new
def x.homu
	"x.homu"
end
p x.homu
# => "x.homu"

p x.singleton_class.prepend Mod
p x.homu
# => "Mod#homu"

# 特異クラスよりも前に挿入されている事がわかる
p x.singleton_class.ancestors
# => [Mod, #<Class:#<X:0x00000001094dd0>>, X, Object, Kernel, BasicObject]
```

滅多にないと思うが一番優先したいメソッドを定義したい場合はこういう手段が考えられる。  


## まとめ

* メソッド呼び出しの優先順位は継承リストに依存する
* 継承リストは `Module#ancestors` で確認できる
* 継承も mixin も継承リストにクラス/モジュールを挿入する機能に過ぎない
* `#include` はクラスよりも前に継承リストに挿入される
* `#prepend` はクラスよりも後に継承リストに挿入される
* 特異メソッドは特異クラスに定義される
* 特異クラスは継承リストの一番手間に挿入されるのでメソッド探査の優先順位が一番高い
* メソッドの探査はレシーバの『特異クラスの』継承リストに依存する
* `#extend` は特異クラスの継承リストに挿入される

このように継承や mixin は継承リストが中心となって機能していることがみてとれる。  
メソッド探査のまとめは以下のようになる。  

```
スーパークラス
↑
include したモジュール
↑
自身のクラス
↑
prepend したモジュール
↑
インスタンスが extend したモジュール
↑
インスタンスの特異クラス(特異メソッド)
```

Ruby では外部で定義されているメソッドを取り込む機能として継承や `include`、`prepend` など手段が分散されており複雑なので理解するのが難しい。  
しかし、継承リスト(`#ancestors`)を参照することでなんとなく仕組みがわかったのではないだろうか。  
もし、意図しないメソッドが呼び出される問題が発生した場合は継承リストを確認してみるとよいだろう。  
ちなみに特異クラスでも `#include` や `#prepend` を行うことができるので、継承リストがどうなるのか気になるのであれば手元で試してみるとよいだろう。 

## 参照

* [rubyのオブジェクトのメソッド探索ルール - xengineer’s diary](http://xengineer.hatenablog.com/entry/2015/11/18/ruby%E3%81%A7%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E6%8E%A2%E7%B4%A2%E3%83%AB%E3%83%BC%E3%83%AB)
* [【ruby】 メソッド探索から見る、モジュール・特異メソッド・特異クラス - 圧倒亭グランパのブログ](http://at-grandpa.hatenablog.jp/entry/2016/02/14/090544)
* [いつも忘れるRubyの include, prepend, extend の意味。そしてActiveSupport::Concernについても。 - Qiita](http://qiita.com/leon-joel/items/f7c4643023f44def5ebd)