【Ruby】クラス・モジュールについて理解を深める

# クラス

## クラスの定義

```rb
class Foo
  # 初期化時に実行されるメソッド
  def initialize(x)
    # インスタンス変数に値を保持
    @x = x
  end

  # インスタンスメソッド
  def method1
    @x
  end
end
```

:::note
<INS>クラス名は**定数**である</INS>ため、大文字で始めないと構文エラーとなる。
┗ 定数の中にクラスを格納している。
:::


## クラス変数
```rb
class Foo
  def initialize
    @@i = 1
  end

  def return_i
    @@i
  end
end

class SubFoo < Foo
  def initialize
    @@i = 100
  end
end

foo = Foo.new
p foo.return_i #=> 1

sub_foo = SubFoo.new

p foo.return_i #=> 100
p sub_foo.return_i #=> 100
```

:::note
クラス定義内やクラスメソッド内で定義され、クラスおよびそのサブクラスのすべてのインスタンスで共有される。
:::
:::note warn
クラス変数の変更は、すべてのインスタンスに影響を与えるため、意図しない変更を引き起こす可能性について注意する必要がある。
:::


## グローバル変数
```rb
$n = 1

class Foo
  $i = 2

  def method1; $n; end
  def method2; $i; end
end

foo = Foo.new

p foo.method1 #=> 1
p foo.method2 #=> 2
```
:::note
どこからでも参照することが可能。
:::
:::note warn
グローバル変数の乱用は、コードの可読性を低下させ、バグの原因を引き起こす可能性がある。
:::

:::note warn
必要な場合にのみ使用する。※出来る限り使用は避けるべき
:::


## インスタンス変数
```rb
@n = 1

class Foo
  # attr_accessor :n, :i
  @i = 2

  # def initialize
  #   @n = 1
  #   @i = 2
  # end

  def method1; @n; end
  def method2; @i; end
end

foo = Foo.new

# foo.n = 1
# foo.i = 2

p foo.method1 #=> nil
p foo.method2 #=> nil
```

:::note
クラスオブジェクトのインスタンスごとに独自の値を持つ変数。
:::

:::note warn
初期化されていない場合は、nilを返す。
:::



## クラスの継承
```rb
class Foo
  def initialize(x)
    @x = x
  end

  def method1
    @x
  end
end

class SubFoo < Foo
  def initialize(x, y)
    @y = y

    # スーパークラスの初期化メソッドを呼び出す
    super x
  end

  def method2
    @x + @y
  end
end

# インスタンス化
sub_foo = SubFoo.new(3, 7)

p SubFoo.superclass #=> Foo
p sub_foo.class     #=> SubFoo
p sub_foo.method1   #=> 3
p sub_foo.method2   #=> 10
# p sub_foo.method3   #=> NoMethodError
```

:::note
***class サブクラス < スーパークラス*** 
で、指定したオブジェクトを継承したクラスオブジェクトを生成する。
:::

:::note warn
Rubyにおいては、スーパークラスを暗黙的に呼び出すことはない。
:::

:::note
クラスからオブジェクトを生成することを**インスタンス化**と呼ぶ。
:::

:::note
継承したクラスのインスタンスは、スーパークラスのインスタンスメソッドを呼び出すことが出来る。
:::

:::note
メモリ上では、以下に保持される。
- インスタンスメソッドは、クラスオブジェクト
- インスタンス変数は、インスタンス
:::

## クラスの継承チェーン
```rb
p Foo.ancestors    #=> [Foo, Object, Kernel, BasicObject]
p SubFoo.ancestors #=> [SubFoo, Foo, Object, Kernel, BasicObject]
```

:::note
上記の通り、継承は***BasicObject***クラスから始まっている。
:::
:::note
スーパークラスを省略してクラスを定義すると、自動的に***Object***クラスを継承する。
:::

[Module#ancestorsについてはこちら](https://docs.ruby-lang.org/ja/latest/method/Module/i/ancestors.html)




## インスタンスメソッドの探索経路
:::note
呼び出し元のクラスに、メソッドが存在するかを判定
→次に、そのスーパクラス（継承元）にメソッドが存在するかの順で判定している
:::

:::note alert
最後まで見つからなかった場合は、`NoMethodError`となる
:::

※例外の`NoMethodError`は、***BasicObject***クラスの[#method_missing](https://docs.ruby-lang.org/ja/latest/method/BasicObject/i/method_missing.html)が呼び出している。

```rb:method_missingをモンキーパッチしてみる
class Foo; end

class SubFoo < Foo
  def initialize(x, y)
    @x = x
    @y = y
  end

  def method_missing(name)
    # 独自の記述追加
    puts "******************** method_missing args name: #{name} ********************"

    # スーパークラスを呼び出して例外を発生させる
    super
  end
end

sub_foo = SubFoo.new(3, 7)

p sub_foo.method3 #=> ******************** method_missing args name: method3 ********************
                  #=> NoMethodError
```

## インスタンスメソッドのネスト
```rb
class Foo
  def method1
    def nested_method1
      100
    end

    1
  end
end

# 定義されているメソッドを確認
p Foo.instance_methods(false) #=> [:method1] ⭐️この時点では、nested_method1は定義されていない扱い

foo = Foo.new
# p foo.nested_method1 #=> NoMethodError ⭐️まだ未定義なのでNoMethodErrorとなる
p foo.method1 #=> 1

p Foo.instance_methods(false) #=> [:method1, :nested_method1] ⭐️method1が実行されたことによって定義された
p foo.nested_method1 #=> 100
```

:::note
ネストされたメソッドは、外側のメソッドが呼び出されることで使うことが出来るようになる。
:::

[Module#instance_methodsはこちら](https://docs.ruby-lang.org/ja/latest/method/Module/i/instance_methods.html)



## メソッドの可視性（アクセス制限 / カプセル化）

|修飾子|説明|
|---|---|
|***public*** |同じクラスの、どのインスタンスからでもアクセス可能。|
|***protected***  |同じクラスと、そのサブクラスからのみアクセス可能。|
|***private***|レシーバを付けた場合は`NoMethodError`となる。また、レシーバを付けない場合は、同じクラスと（Rubyでは）そのサブクラスからアクセス可能。|

```rb
class Foo
  def public_method
    puts 'Public'
  end

  def all_method
    obj = Foo.new

    obj.public_method
    obj.protected_method
    # obj.private_method # レシーバ付きの場合は、NoMethodErrorが発生
    private_method # レシーバを外せば、NoMethodErrorは発生しない
  end


  protected

  def protected_method
    puts 'Protected'
  end


  private

  def private_method
    puts 'Private'
  end
end

class SubFoo < Foo
  def access_protected_method
    protected_method
  end

  def access_private_method
    private_method
  end
end

sub_foo = SubFoo.new
sub_foo.access_protected_method #=> Protected
sub_foo.access_private_method #=> Private
# sub_foo.protected_method #=> NoMethodError
# sub_foo.private_method #=> NoMethodError

foo = Foo.new
foo.public_method #=> Public

foo.all_method #=> Public
               #=> Protected
               #=> Private

# インスタンスメソッドとして、直接呼び出すことは出来ない
# foo.protected_method #=> NoMethodError
# foo.private_method #=> NoMethodError
```

### アクセス制限の変更
```rb
class Foo
  private

  def private_method1
    puts 'Private1'
  end

  def private_method2
    puts 'Private2'
  end
end

class SubFoo < Foo
  # privateから、publicに変更
  public :private_method1, :private_method2
end

sub_foo = SubFoo.new
sub_foo.private_method1 #=> Private1
sub_foo.private_method2 #=> Private2
```

:::note
public, protected, privateのいずれかを指定し、メソッド名を`識別子` or `シンボル`で記載する。複数のメソッドのアクセス制限を変更する場合は、カンマで区切る。
:::


## `alias`式 と `undef`式
```rb
class SubFoo < Foo
  def initialize(x, y)
    @y = y
    super x
  end

  def method2
    @x + @y
  end

  def method3; 1; end

  # 新しいメソッド名 古いメソッド名
  alias :sub_method_a :method2
  alias :sub_method_b :method3

  # method3の定義を取り消す
  undef :method3
end

sub_foo = SubFoo.new(3, 7)
p sub_foo.sub_method_a #=> 10
p sub_foo.sub_method_b #=> 1
# p sub_foo.method3 #=> NoMethodError
```

:::note
alias式で別名をつける/ undef式で定義を取り消す際のメソッド名は、
`識別子` or `シンボル`で指定する。
:::

:::note warn
aliasが定義されている場合に、undef式で片方の定義を取り消しても、もう片方が実行出来てしまう。
:::


## オープンクラス

```rb
class Foo
  def method1; 1; end
end

class Foo
  def method2; 2; end
end

p Foo.instance_methods(false) #=> [:method1, :method2]
```

:::note
同名のクラスを複数回定義しても、エラーにならない。
:::

:::note
定義済みのクラスを再定義出来るクラスのことを、***オープンクラス***と呼ぶ。
:::


## 特異クラス（Singleton Class）
- 特異クラスとは、特定のインスタンスだけに適用される特別なクラス
  - シングルトンクラスとも呼ばれる

```rb
class Foo; end

foo = Foo.new

singleton_foo = class << foo
  self # 特異クラス自身を返す
end

p singleton_foo #=> #<Class:#<Foo:0x000000010af94d30>>
p singleton_foo.ancestors #=> [#<Class:#<Foo:0x000000010d094d00>>, Foo, Object, Kernel, BasicObject]
p singleton_foo.superclass #=> Foo
```

:::note
特異クラスは、`class << 対象のオブジェクト`で定義することが出来る。
:::

:::note
Rubyインタプリタは、特異クラスを作成して継承チェーンに組み込む。
※インスタンスの生成元のクラスを、スーパークラスとしている。
:::

- Rubyインタプリタ
  - プログラムのソースコードを読み取り、実行可能な形式に変換して実行するプログラム
  - つまり、プログラムを実行するためのプログラムのこと


## 特異メソッド（Singleton Method）
- オブジェクトの、特定のインスタンスに対して定義されたメソッド
- 特定のインスタンスにのみ影響を与え、そのインスタンスのみで呼び出すことが出来る
- 通常、特異メソッドは、あるインスタンスに対してのみ必要な振る舞いを追加するために使用される


### 特異メソッドの定義方法1
```rb
class Foo
  def initialize(num)
    @num = num
  end
end

foo1 = Foo.new(11)
foo3 = Foo.new(33)

# 定義方法①
def foo1.method1
  @num += 100
end

p foo1.method1 #=> 111
# p foo3.method1 #=> NoMethodError
```

### 特異メソッドの定義方法2
```rb
class Foo
  def initialize(num)
    @num = num
  end
end

foo2 = Foo.new(22)
foo3 = Foo.new(33)

# 定義方法②
class << foo2
  def method2
    @num += 200
  end
end

p foo2.method2 #=> 222
# p foo3.method2 #=> NoMethodError
```


:::note
特定のインスタンスにだけ、特別な性質を持たせたい場面で使用することが出来る。
:::




## クラスメソッド
- クラス自体に関連付けられたメソッド
- クラスオブジェクトをレシーバとする


### クラスメソッドの定義方法1
```rb
class Foo
  def Foo.method1
    1
  end
end

p Foo.method1 #=> 1
```

### クラスメソッドの定義方法2
```rb
class Foo
  def self.method2
    2
  end
end

p Foo.method2 #=> 2
```

### クラスメソッドの定義方法3
```rb
class Foo
  class << self
    def method3
      3
    end
  end
end

p Foo.method3 #=> 3
```


# モジュール

- モジュールの目的
  - 名前空間の提供と、`Mix-in`における機能の定義

:::note
***Mix-in***：クラスに、モジュールの機能を混ぜ合わせること。

クラスは多重継承が出来ないが、***Mix-in***をすることで複数の機能を使うことが出来る。（***Mix-in***によって、多重継承と同等の役割を実現している）
:::

:::note warn
モジュールからインスタンス化（new）することは出来ない。
:::

:::note warn
モジュールは、継承することが出来ない。
:::

:::note
モジュールは、クラスや他のモジュールに取り込むことが出来る。
:::

:::note
クラス自体にモジュールのメソッドが混ざるのではなく、[モジュールに対応した無名クラスがスーパークラスとの間の継承チェーンに挿入される](https://qiita.com/royal_straight_flush/items/ac6ed8993debbc070551#%E7%B6%99%E6%89%BF%E3%83%81%E3%82%A7%E3%83%BC%E3%83%B3%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%82%92%E5%8F%96%E3%82%8A%E8%BE%BC%E3%82%93%E3%81%A0%E5%A0%B4%E5%90%88)。
:::

## メソッドの探索経路（モジュールを組み込んだ場合）

```rb:共通コード
module Module1
  def method1; 1; end
  def method2; 2; end
  def method3; 3; end
  def method4; 4; end
end

module Module2
  def method1; 10; end
  def method2; 20; end
  def method4; 40; end
end

class Foo
  def method1; 100; end
  def method4; 400; end
  def method5; 500; end
end
```


### [Module#include](https://docs.ruby-lang.org/ja/latest/method/Module/i/include.html)の場合

```rb:includeでMix-in
class SubFoo < Foo
  include Module1
  include Module2

  def method1; 1000; end
end

sub_foo = SubFoo.new

p sub_foo.method1 #=> 1000
p sub_foo.method2 #=> 20
p sub_foo.method3 #=> 3
p sub_foo.method4 #=> 40
p sub_foo.method5 #=> 500
```

:::note
***include***の場合、呼び出し元のクラス（SubFoo）→Module2→Module1→呼び出し元のスーパークラス（Foo)の順にメソッド探索が行われる。
:::


### [Module#prepend](https://docs.ruby-lang.org/ja/latest/method/Module/i/prepend.html)の場合（Ruby2.0~）
```rb:prependでMix-in
class SubFoo < Foo
  prepend Module1
  prepend Module2

  def method1; 1000; end
end

sub_foo = SubFoo.new

p sub_foo.method1 #=> 10
p sub_foo.method2 #=> 20
p sub_foo.method3 #=> 3
p sub_foo.method4 #=> 40
p sub_foo.method5 #=> 500
```

:::note
***prepend***の場合、呼び出し元のクラス（SubFoo）よりも先に、最後にprependされたモジュールから順（Module2→Module1→SubFoo→Foo）にメソッド探索が行われる。
:::


## 継承チェーン（モジュールを取り込んだ場合）

```rb:include
p SubFoo.ancestors  #=> [SubFoo, Module2, Module1, Foo, Object, Kernel, BasicObject]
p SubFoo.superclass #=> Foo
```


```rb:prepend
p SubFoo.ancestors  #=> [Module2, Module1, SubFoo, Foo, Object, Kernel, BasicObject]
p SubFoo.superclass #=> Foo
```

- 継承チェーンには、include/prependした`Module1`・`Module2`が含まれている
- 但し、`SubFoo`のスーパークラスは`Foo`のまま

:::note
moduleを***include***すると、Rubyインタプリタは指定されたモジュールに対応する無名クラスを作成し、スーパークラスとの間に組み入れる。
対して、***prepend***した場合は先頭に組み入れている。
:::

## [Object#extend](https://docs.ruby-lang.org/ja/latest/method/Object/i/extend.html)

- 特異クラスへの`Mix-in`で使用するメソッド

:::note
>[Module#include](https://docs.ruby-lang.org/ja/latest/method/Module/i/include.html) は、クラス(のインスタンス)に機能を追加しますが、extend は、ある特定のオブジェクトだけにモジュールの機能を追加したいときに使用します。
:::

```rb:共通コード
module Module1
  def method1; @num += 700; end
end

class Foo
  def initialize(num)
    @num = num
  end
end

foo1 = Foo.new(77)
```

```rb:extendを使わずに特異クラスを定義した場合
class << foo1
  include Module1
end

p foo1.method1 #=> 777
```

```rb:extendを使用した場合
foo1.extend(Module1)

p foo1.method1 #=> 777
```

## [Module#refine](https://docs.ruby-lang.org/ja/latest/method/Module/i/refine.html)（Ruby2.1~）
- 限られた範囲のみ、`Mix-in`を有効化したい場合に使用
>引数 klass で指定したクラスまたはモジュールだけに対して、ブロックで指定した機能を提供できるモジュールを定義します。定義した機能は Module#refine を使用せずに直接 klass に対して変更を行う場合と異なり、限られた範囲のみ有効にできます。そのため、既存の機能を局所的に修正したい場合などに用いる事ができます。

```rb
class Foo
  def method1; 1; end
  def method2; 2; end
end

module Module1
  # 変更を加えるクラスを宣言
  refine Foo do
    # 変更するメソッド及び処理を記述
    def method1; 10000; end
  end
end

foo = Foo.new

p foo.method1 #=> 1
p foo.method2 #=> 2

# usingを呼び出し、変更したメソッドの呼び出しを有効化する
using Module1

p foo.method1 #=> 10000
p foo.method2 #=> 2
```

:::note
usingが有効になる範囲は、クラス・モジュール定義の
- 外でusingを呼び出した場合はファイルの末尾まで
- 中でusingを呼び出した場合はそのクラス・モジュール定義の末尾まで
:::


## `include` と `extend` 両方使う場合

```rb
module Module1
  def method1; 1; end
end

class Foo
  include Module1
  extend Module1
end

foo = Foo.new
p foo.method1 #=> 1
p Foo.method1 #=> 1
```
- `include`→モジュール内のメソッドが インスタンスメソッドとして動作する
- `extend`→モジュール内のメソッドがクラスメソッドとして動作する


:::note
インスタンスメソッドとクラスメソッドの両方として利用可能になる。
:::


## ネストした定数の定義・参照
```rb
module Module1; end

# Module1の中に、Numberを宣言
Module1::Number = 100

class Foo
  include Module1
  # 警告が出るが、値を書き換えること自体は可能
  Module1::Number = 200

  def number
    Module1::Number
  end
end

# インスタンスを生成しなくても、値は上書きされる
p Module1::Number #=> 200

foo = Foo.new
p foo.number #=> 200
```

:::note
モジュールやクラスオブジェクトが格納された定数に対しては、`::`という演算子を使用することで、定数のネストを指定することが可能。
:::

:::note warn
Rubyでは柔軟性を高めるため、定数に値を再代入することが可能となっている。
ただし、本来は再代入を避けるべきである。
:::


## 名前空間
```rb:モジュールの中で、クラスを分けている例
module Food
  class Ramen
    def name; 'ラーメン'; end
  end

  class Cake
    def name; 'ケーキ'; end
  end
end

puts Food::Ramen.new.name #=> ラーメン
puts Food::Cake.new.name #=> ケーキ
```

```rb:同じクラス名だが、モジュールで名前の衝突を防いでいる例
module Guest
  class User
    def name; 'ゲストユーザ'; end
  end
end

module Admin
  class User
    def name; '管理者'; end
  end
end

puts Guest::User.new.name #=> ゲストユーザ
puts Admin::User.new.name #=> 管理者
```

:::note
同じ名前を持つクラスや定数が存在しても、名前の衝突を防ぐことが出来る。
:::
:::note
関連するクラスや定数をまとめてグループ化し、構造を整理することが出来る。
:::
:::note
適切な名前空間を使用することで、クラスや定数の名前に意味を与えることが出来る。
:::
:::note
意味や目的毎にモジュールを分けてクラスを定義することが大切。
:::