おもしろメタプログラミング/５章

# 木曜日 : クラス定義

## できるようになること
- クラスマクロ
- アラウンドエイリアス

## カレントクラス
カレントクラスを考える前にカレントオブジェクトの概念についておさらいします
rubyにはカレントオブジェクトselfが常にある

### selfのおさらい
`レシーバを明示しないメソッドのレシーバはselfになる`

`selfは次の3つのタイミングで変化する`
> class
> module
> def

```
self # => main

class MyClass

  self  # => MyClass 
  
  def my_method
    self
  end
  
end

obj = MyClass.new
obj.my_method  # => obj

```

### カレントオブジェクトと同様に`カレントクラス`も存在する
カレントクラスはメソッド定義を支配しています
`defによるメソッド定義はカレントクラスに対して行われる`
`カレントクラスはclassキーワードで切り替わる`

### class_eval
instance_evalのclass版、class_evalも存在する

instance_evalはカレントオブジェクトをレシーバにしてブロックを実行する

```
class MyClass
  def my_method
    @v = 1
  end
end

@v = 2

obj = MyClass.new
obj.my_method

obj.instance_eval do
  self  # => obj
  @v  # => ?
end
```

同じような感じで、
class_evalはカレントクラスをレシーバにしてブロックを実行する
-> クラスを再オープンしてメソッドを定義するのに使われる

``` 
module MyClass
  extend MyModule
  def self.included(klass)
    klass.class_eval do
      def self.my_method # <= クラスメソッドを追加している
        # code...
      end
    end
    # code...
  end
end
```
上のコードでは、klassに対してclass_evalでクラスを再オープンすることで
klassにさらにメソッドを追加している

classキーワードで開けない、つまりクラスの名前が分からないとき(引数で渡ってくるときなど)に
メソッドを追加するのに使われる


## 特異メソッドの紹介

`オブジェクトにクラスにはない独自のメソッドを追加することができる。`
これを特異メソッドと呼ぶ。

使い方

```
str = "HELLO"

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

str.title?  # => true
str.singleton_methods  # => [:title?]
```

実は、クラスメソッドはクラスの特異メソッドにすぎない

```
class MyClass
  def self.my_singleton  # class定義の中では self == MyClass
    puts "this is a singleton method!"
  end
end
```

## 特異クラス

特異メソッドがどこに属しているか不思議じゃないですか？
- メソッド探索的にはインスタンスのクラスにメソッドが追加されてなくちゃおかしい
- でもクラスに追加されてたら他のインスタンスからも呼べてしまう…

実は、
すべてのオブジェクトは独自のクラス、特異クラスを持っている

```
class MyClass; end

obj = MyClass.new
obj.class  # => MyClass
obj.singleton_class  # => #<Class:#<MyClass:0x007ff227894650>>
```
特異クラスは#singleton_classで調べられる

メソッド探索の順番は
`objの特異クラス　-> MyClass -> ...`
となる

class定義の中での特異クラスの呼び方

```
class MyClass
  class << self  # selfの特異クラスを開く == カレントクラスをselfの特異クラスにする
    def my_singleton_method
      puts "資料作るの大変…"
    end
  end
end
```
`class << hoge`で特異クラスを開くことができる
上のコードはクラスメソッドを定義しているのと同じ
(クラスメソッド == クラスの特異メソッド)

## クラスマクロ
`attr_accessor`、`attr_reader`などクラス定義の中でキーワードのように呼べるメソッドがある
このようなメソッドは`クラスマクロ`と呼ばれている

でも、
クラスマクロは、単なるクラスメソッド。

```
class MyClass
  attr_accessor :id, :name  # self.attr_accessorを呼んでるだけ
  
  def initialize(id, name)
    @id = id
    @name = name
  end
end
```

だからクラスメソッドを定義すれば簡単に`クラスマクロ`を作ることができる

## includeとextendについて

特異クラスを開いているクラスマクロの例の一つが、extend

include <- includeしたクラスにインスタンスメソッドを拡張する
extend  <- extendしたクラスにクラスメソッドを拡張する

extendを使わずにクラスメソッドを拡張してみる

```
module MyModule
  def my_method; 'hello'; end
end

obj = Object.new

class << obj
  include MyModule
end

obj.my_method  # => 'hello'
```

これは下のコードと同じことをしている

```
module MyModule
  def my_method; 'hello'; end
end

obj = Object.new
obj.extend MyModule
obj.my_method # => 'hello'
```

## アラウンドエイリアス
クラスマクロ`Module#alias_method`を使ってメソッドをエイリアスをする(別名をつける)ことができる

```
class MyClass
  def my_method; 'hello'; end
  alias_method :m, :my_method
end

obj = MyClass.new
obj.my_method  # => 'hello'
obj.m  # => 'hello'
```
上のコードでは`my_method`に別名`m`を与えている
古い名前でも呼ぶことができる

String#sizeはString#lengthのエイリアスらしい(なんで必要だったんだろう)

エイリアスを有効に使うと以下のようにメソッドを名前を変えずに再定義することができる

```
class String
  alias_method :real_length, :length
  # def real_lenght
  #   length
  # end
  
  def length 
    real_length > 5 ? 'long' : 'short'
  end
  # lengthをオーバライド
end
 
"hello hello hello".length  # => "long"
"hello hello hello".real_length  # => 17
```
 
上のコードでは
alias_methodでlengthのエイリアスreal_lengthを作成し、
def lengthを開いてオーバライドすることで
lengthを再定義している

このような手法を`アラウンドエイリアス`と呼ぶ
アラウンドエイリアスの手順をまとめる
1. メソッドにエイリアスをつける
2. メソッドを再定義する
3. 新しいメソッドから古いメソッドを呼ぶ

 
## ちょっとした演習
### 問題1
クラスマクロ'alias_method'を実装してみて正しく動くことを確認する

### 問題2
(メタプロの本から引用)
Fixnum#+を再定義して足し算の結果に必ず+1されるようにする
例えば

```
1 + 1   # => 3
3 + 4   # => 8
```
という感じ