読書ログ『メタプログラミング Ruby 第2版』5章

## 5章: 木曜日: クラス定義

はじめに

- Rubyのclassキーワードは、オブジェクトの動作規定だけでなく、実際に**コードを実行**している
- この章では、クラスマクロ(クラスを修正する方法) / アラウンドエイリアス(メソッドをコードでラップする方法) / 特異クラス(シングルトンクラス) を説明する

## クラス定義とは

- クラス定義にはあらゆるコードを置くことができる
  - メソッドを定義するだけの場所ではない

```ruby
class MyClass
	puts 'Hello'
end
```

- メソッドやブロックと同じように、クラス定義も最後の命令文の値を戻す
- クラス(やモジュール)定義の中では、クラスがカレントオブジェクトselfになる
  - クラスとモジュールも単なるオブジェクトにすぎない。

```ruby
result = class Myclass
	self
end

result #=> MyClass
```

## カレントクラス

- Rubyのプログラムは、常にカレントオブジェクトselfと **カレントクラス**を持つ
  - メソッドを定義すると、カレントクラスのインスタンスメソッドに
- プログラムのトップレベルでは、カレントクラスは、mainのクラスのObjectになる。
- メソッドの中では、カレントオブジェクトのクラスが、カレントクラスに。

### カレントオブジェクト

- self
- インスタンスメソッドが呼び出されるレシーバのこと

### Module#class_eval

- クラス名がわからない時にクラスをオープンするために用いる
  - クラスのコンテキストでブロックを評価するもの

```ruby
def add_method_to(a_class)
	a_class.class_eval do
		def m; 'Hello!'; end
	end
end

add_method_to Strign
"abc".m #=> "Hello!"
"abc".m.class #=> String
```

- class_evalは、selfとカレントクラスを変更する
- カレントクラスを変更することで、クラスを再オープンできる
- classが定数を必要とするのに対し、class_evalはクラスを参照する変数ならなんでも使える
- class_evalは、フラットスコープを持つので、class_evalブロックのスコープの外側の変数も参照できる

### instance_evalとclass_evalの使い分け

- クラス以外のオブジェクトをオープンしたいなら、**instance_eval**
- クラス定義をオープンして、defを使ってメソッドを定義したいなら、**class_eval**

### カレントクラスのまとめ

- Rubyのインタプリタは、常に**カレントクラス**の参照を追跡
  - defで定義された全てのメソッドは、カレントクラスのインスタンスメソッドに。
- クラス定義の中では、カレントオブジェクトselfとカレントクラス(定義クラス)は同じ
- クラスへの参照を持っていれば、クラスはclass_evalでオープンできる

## クラスインスタンス変数

- **クラスのインスタンス変数と、クラスのオブジェクトのインスタンス変数は別物**
- クラスのスコープで定義した変数のことを「クラスインスタンス変数」
- メソッドのスコープで定義した変数を「インスタンス変数」
  - [参考](https://qiita.com/ksh-fthr/items/27a894fa7e04699fa08f#結論)　この記事がすごく参考になりました。

- アクセスできるのはクラスだけであり、クラスのインスタンスやサブクラスからはアクセスできない

```ruby
class MyClass
	@my_var = 1
	def self.read; @my_var; end
	def write; @my_var = 2; end
	def read; @my_var; end
end

obj = MyClass.new
# インスタンスメソッドでアクセスできない。
obj.read #=> nil
obj.write
# インスタンスメソッドwriteで定義したものを呼び出す
obj.read #=> 2
# クラスメソッドで呼び出せる！
MyClass.read #=> 1 
```

### クラス変数

- @@プレフィックスをつけたもの

  ```ruby
  class C
  	@@v = 1
  end
  ```

- クラス変数は、サブクラスや通常のインスタンスメソッドからもアクセスできる
- クラス変数は、クラスではなく **クラス階層**に属している
  - ハマることがあるので、**Rubyistはクラス変数を使わず、クラスインスタンス変数を推奨**

## 特異メソッド

- **単一のオブジェクトに特化したメソッド**
  - 特定のオブジェクトにメソッドを追加できる
- Object#define_singleton_methodで定義できる

```ruby
str = "just a regular string"

def str.title?
	self.upcase == self
end

self.title? #=> false
str.methods.grep(/title?/)	#=> [:title?]
str.singleton_methods				#=> [:title?]
```

### Rubyにおける型

- 「型」はオブジェクトが反応するメソッドの集合に過ぎない
  - Rubyなどの動的言語では、オブジェクトの「型」はそのクラスとは厳密に結びついていない
- ダックタイピング
  - **「型」はオブジェクトが反応するメソッドの集合に過ぎない**という、流動的な型のこと。

### クラスメソッドの真実

- **クラスは単なるオブジェクトであり、クラス名は単なる定数**
- クラスメソッドは、**クラスの特異メソッド**

### クラスマクロ

- **クラス定義の中で使えるクラスメソッド**
  - ex) attr_accessor

## 特異クラス

- オブジェクトの通常クラスとは別に持つ、特別なクラスのこと
  - メタクラスやシングルトンクラスとも
- **特異クラスは、オブジェクトの特異メソッドが住んでいる場所**
  - `Object#singleton_class` か、 `class << 構文` によってみることができる
- 特異クラスは、インスタンスを一つしか持てない
  - だから、 **シングルトンクラス**
- **オブジェクトが特異メソッドを持っていると**、Rubyは通常のクラスではなく、**特異クラスのメソッド**から探索を始める。

### Rubyのオブジェクトモデルの7つのルール

- オブジェクトは1種類しかない
  - それが、通常のオブジェクトかモジュールに
- モジュールは1種類しかない
  - それが通常のモジュール、クラス、特異クラスのいずれかに
- メソッドは１種類しかない
  - メソッドはモジュール(大半はクラス)に住んでいる
- 全てのオブジェクト(クラス含む)は、「本物のクラス」を持つ
  - それが通常のクラスか特異クラス
- 全てのクラス(BasicObjectを除く)は、一つの祖先を持つ
  - あらゆるクラスがBasicObjectに向かって、１本の継承チェーンをもつ。
- オブジェクトの特異クラスのスーパークラスは、オブジェクトのクラス
  - クラスの特異クラスのスーパークラスは、クラスのスーパークラスの特異クラス
- メソッドを呼び出すときは、Rubyはレシーバの本物のクラスに向かって、「右へ」煤sみ、継承チェーンを「上へ」進む。

### クラスメソッドの構文

- self.メソッドを使う場合

```ruby
class MyClass
	def self.another_class_method; end
end
```

- << self を使う場合

```ruby
class MyClass
	class << self
		def yet_another_class_method; end
	end
end
```

## メソッドラッパー

### アラウンドエイリアス

- Module#alias_methodを使って、Rubyのメソッドにエイリアスをつける
  - alias_methodを使うときは、**メソッドの新しい名前を先に、元の名前を後に**

```ruby
class myClass
	def my_method; 'my_method()'; end
	alias_method :m, :my_method
end

obj = MyClass.new
obj.my_method	#=> "my_method()"
obj.m					#=> "my_method()"
```

### メソッドの再定義

- 新しいメソッドを定義して、元の名前をつけること
  - 元のメソッドを変更することではない

### アラウンドエイリアスをまとめると

- メソッドにエイリアスをつける
- メソッドを再定義する
- 新しいメソッドから古いメソッドを呼び出す。

## 感想

む、、、むずい。なんとか食いついたという感覚:sweat:

時間を置いてもう一度読み直す。