木曜日 : クラス定義
できるようになること
- クラスマクロ
- アラウンドエイリアス
カレントクラス
カレントクラスを考える前にカレントオブジェクトの概念についておさらいします
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
という感じ