クラス
クラス定義
- クラスからオブジェクト(インスタンス)を作る
- インスタンスって言ったりオブジェクトって言ったり書いたりするので、そこは適当に読み分けて下さい
- クラス名は大文字で始めなければならない。インタプリタは(ちなみにObjectクラスへ)定数を定義し、そこにクラスを格納するからである
- 蛇足 - Rubyのクラスはオブジェクトである。定数に紐付けられた、Classクラスのインスタンスである
class Moko
def initialize(a)
@a = a
end
def method1
@a
end
end
moko1 = Moko.new(1)
moko2 = Moko.new(2)
moko1.method1
=> 1
moko2.method1
=> 2
クラスは必ず定数に一度は紐付けられるが、その後は煮ようと焼こうと自由である
class Hage
end
hage = Hage
Hage = nil
Object.send(:remove_const, :Hage) # Hage定数完全削除できそうかな
hage.new.class
=> Hage
- class式が評価されると、クラス定義の内部が評価される
- ちなみにclass式の戻り値は内部で最後に評価したもの。当然っちゃ当然だけど何かできそうだね?selfを返したり
class Moko
p 'moko1'
def method1
p 'method1'
end
p 'moko2'
end
"moko1"
"moko2"
=> "moko2"
Moko.new.method1
"method1"
=> "method1"
インスタンスメソッド
知ってるって?まぁそう言わずに
- class式の中に普通にdefすればインスタンスメソッドとなり、そのクラスのオブジェクトから呼び出せる
- initializeはインスタンスメソッドでありコンストラクタ。クラスがnewされた時に自動で呼ばれる。初期化の引数を受け付けるぜ!という気概を感じる。自動でprivateなメソッドになる
定義時でも呼び出し時でもカッコは省略できる でもそんな事はしない
def hage a, b
p a, b
end
hage 1, 2
1
2
=> [1, 2]
仮引数のデフォルト値
基本的にメソッド呼び出し時の引数の数はピッタリ合わないと例外を出す
def hage(a, b)
p a, b
end
hage(1)
ArgumentError: wrong number of arguments (given 1, expected 2)
def hage(a, b = 2)
p a, b
end
hage(1)
1
2
=> [1, 2]
キーワード引数
2.0から使えるんですけどね
Hash の辺りも重要
def hage(a:, b:)
p a, b
end
hage(a: 1, b: 2)
1
2
=> [1, 2]
# ハッシュで引数を渡すが、ハッシュ定義の {} を省略できる、と思えばいい?
hage({a: 1, b: 2})
1
2
=> [1, 2]
デフォルト値もいける
def hage(a: 1, b:)
p a, b
end
hage(b: 2)
1
2
=> [1, 2]
# デフォルト値を設定していないキーワードを指定していないと例外発生
hage(a: 2)
ArgumentError: missing keyword: b
# キーワード引数に存在しないキーワードを渡すと例外発生
hage(b: 1, c: 3)
ArgumentError: unknown keyword: c
# なかなか厳しい、想定しているキーワードのみの使用を強制される
もちろん1つのハッシュとして引数を引き受けることも出来る、1.9まではこうやって記述していた気がする(一般的かどうかは知らん)
def hage(options)
p options
end
hage(a:1, b: 2)
=> {:a=>1, :b=>2}
- 想定しているキーはその名前で受け取るが、想定外のキーでも受け取れるようにしたい
- (↑合言葉: 配列展開や可変長引数は*、残りのキーワード引数の全受付は**)
def hage(a:, **c)
p a, c
end
hage(a:1, b: 2, c: 3)
1
{:b=>2, :c=>3}
=> [1, {:b=>2, :c=>3}]
** は引数のラストにしか書けない
def hage(**c, a:)
end
#=> SyntaxError
デフォルト値とも組み合わせられる
def hage(a: 1, **c)
p a, c
end
hage(b: 2, c: 3)
1
{:b=>2, :c=>3}
=> [1, {:b=>2, :c=>3}]
仮引数は参照渡し
def hage(obu)
obu.object_id
end
b = 'bb'
b.object_id
=> 70271490886920
hage(b)
=> 70271490886920
メソッド内で破壊的メソッドを呼ぶと、呼び出し元の変数が破壊される。当然
配列の多重代入みたいな動きをするメソッド呼び出し
この辺は熱い
def moko((a, b), c)
p [a, b, c]
end
moko([1, 2], 3)
[1, 2, 3]
=> [1, 2, 3]
- 代入元の数が多いけど、全て漏らさず代入したい
- (↑合言葉: 配列展開や可変長引数は*、残りのキーワード引数の全受付は**)
a, b, *c = 10, 20, 30, 40, 50
a #=> 10
b #=> 20
c #=> [30, 40, 50]
- この動作をメソッドの仮引数で使用すると可変長引数を実現できる
- (↑合言葉: 配列展開や可変長引数は*、残りのキーワード引数の全受付は**)
def hage(a, *b)
p a
p b
end
hage(1, 2, 3, 4)
1
[2, 3, 4]
=> [2, 3, 4]
仮引数に渡す実引数で使用すると配列を複数の引数としてメソッドを呼び出せる 引数展開、JavaScriptの Function#apply みたいな動作
as = [1, 2, 3]
def hage(a, *b)
p a
p b
end
hage(*as)
1
[2, 3]
=> [2, 3]
# 普通に呼び出すとこうなってしまう
hage(as)
[1, 2, 3]
[]
=> []
インスタンス変数
知ってるって?
- @a のような変数は、オブジェクトが個別に保持する値
- 内側から参照、変更ができる
- やる気になれば外部からもさわれる
- 変数、定数、予約語、演算子関係 の辺りにもちょっと書いてある
- initializeメソッドで定義される事が多いんじゃないかな
object#class
生成元のクラスを返す
class Moko
end
Moko.new.class
=> Moko
Moko.new.class == Moko
=> true
クラス継承
この辺からソースが無駄に長くなるけどがんばって
- Moko は定義時に < を使って Oyaji を継承している
- Moko は Oyaji から必殺技の書いてある巻物を継承したのだ
- Mokoのinitialize(引数2つ)は
- 1つ目の引数は super を使ってOyajiのinitialize(引数1つ)を呼び出し、@a の保存を行っている
- 2つ目の引数は自分の処理の中で @b へ保存している
- ちなみに super は引数無しで呼び出すと、自分が呼び出された引数そのままでスーパークラスの同名メソッドを呼び出す
- Mokoのmethod1はOyajiから継承したメソッド
- Mokoのmethod2は自前で用意したメソッド
- Oyajiは何も継承していないように見えるが、省略すると自動的にObjectクラスを継承する
- 継承したメソッドでインスタンス変数を初期化すると、なんか継承された側に紐付いたようなインスタンス変数が出来るようなそんなイメージが無いだろうか、でも気にしない。そのインスタンス変数の持ち主はselfだ。selfはそのメソッドを呼び出すきっかけになった、最初に呼び出された自分(レシーバ)だ。インスタンス変数を初期化する技は親父から受け継いだ技かもしれない、しかしその技を今使っているのは君なのだ
class Oyaji
def initialize(a)
@a = a
end
def method1
@a
end
end
class Moko < Oyaji
def initialize(a, b)
@b = b
super(a)
end
def method2(c)
@a + @b + c
end
end
moko = Moko.new(1, 2)
moko.method1
=> 1
moko.method2(3)
=> 6
initializeの継承
- Moko で initialize を定義しないと Oyaji の initialize を継承し、そのまんまコンストラクタとして実行される
- 何を言っているのかというと、この動作は言語によって違うらしいので、Rubyだとこうだよ、ってこった
class Oyaji
def initialize
p 'init!'
end
end
class Moko < Oyaji
end
Moko.new
"init!"
インスタンスメソッド探索経路の話
インスタンスメソッドについて、上記でこんな感じで説明した
- インスタンスは、クラスで定義されたインスタンスメソッドを呼び出すことができる
- スーパークラスを継承したクラスのインスタンスは、継承したスーパークラスのメソッドを呼び出すことができる
** Rubyインタプリタが、呼び出されたインスタンスメソッドがどこに定義してあったのか探索する経路を知るとRubyが分かった気になれる **
クラスのレイヤとインスタンスのレイヤを分けると次のような感じだろうか
Class界 (抽象的方面)
Moko
----------------
インスタンス(オブジェクト)界 (具体的方面)
moko1
moko2
インスタンスメソッドとインスタンス変数がメモリ上ではどこに存在するかというと
- インスタンスメソッドはクラスレイヤに存在する
- インスタンスは自分がどのクラスのインスタンスなのか把握している為、そのクラスのメソッドを呼び出す事が可能
- インスタンス変数はインスタンスレイヤに存在する
- インスタンス毎に違う値なら当然、オブジェクトが抱え込んでいます
Class界
Moko
インスタンスメソッド(method2)
----------------
インスタンス界
moko1
インスタンス変数(@b)
moko2
インスタンス変数(@b)
では、継承しているクラスならどうなる
インスタンス化は縦に表現してましたが、継承関係は横に表現してます
Class界 => superclass =>
Moko ===================================================> Oyaji =========> Object => BasicObject
インスタンスメソッド(method2) インスタンスメソッド(method1)
--------------------------------------------------------------------------------------------------------------------------------
インスタンス(オブジェクト)界 (具体的方面)
moko1 oyaji
インスタンス変数(@a, @b) インスタンス変数(@a)
moko2
インスタンス変数(@a, @b)
メソッド探索経路を辿る
↑上の図で
- oyaji#method1 を呼び出すと 自分の生成元クラス Oyaji にメソッドがあるかどうか判定、存在するので実行されます
- moko1#method2 を呼び出すと 自分の生成元クラス Moko にメソッドがあるかどうか判定、存在するので実行されます
つまり
- Rubyインタプリタは、オブジェクトの生成元クラスにメソッドが定義されていなかった場合、そのスーパークラスに定義されていないか探します
- スーパークラスに無いなら、更にスーパークラスを探しに行きます。最後まで見つからなかった場合、例外:NoMethodErrorを発火します
さらにつまり
- oyaji#method2 を呼び出すと 自分の生成元クラス Oyaji にメソッドがあるかどうか判定、存在しないので次に Object#method2 を探しに行き、存在しないので、、 NoMethodErro例外が発生
- moko1#method1 を呼び出すと 自分の生成元クラス Moko にメソッドがあるかどうか判定、存在しないので次に Oyaji#method1 を探しに行き、存在するので実行されます
クラス継承チェーン
- 今度は縦に継承チェーンを並べた図を用意する
- Oyajiクラスを継承したMokoクラスを定義しただけで、実はコレだけ継承している
- つまり、Moko,クラスのオブジェクトはObjectクラスに定義してあるメソッドも実行できる、という事
- 途中にKernelという謎な奴が居るけど後述するので今は気にしない
BasicObject
↑superclass
Kernel
↑変な奴が居る!
Object
↑superclass
Oyaji =====> oyajiオブジェクト
↑superclass
Moko =====> mokoオブジェクト
継承チェーンを確認する
- Mokoにそんなクラスメソッド(後述)が定義してないよ?↓
- もちろんこのancestorsメソッドも、メソッドを探しながら継承チェーンを駆け登って発見、実行されたのだ
- これから目にする様々なメソッド達も、継承された上の方の何処かに既に実装されているメソッドなのだ
Moko.ancestors
=> [Moko, Oyaji, Object, Kernel, BasicObject]
Oyaji.ancestors
=> [Oyaji, Object, Kernel, BasicObject]
Module.ancestors にも書いてある
クラスの包含関係の比較
- とりあえず、class定義時に継承した時の向きならtrueが返ってくる!って覚えてればいいよ!
- 継承したクラスは継承元のメソッドも使えるし、なんか大きそうじゃね?って間隔は逆で、
- 継承したものは継承元に実装してあるんだから、自分の元に実装されているのは実は思ったよりも少ない、だから Moko < Oyaji って覚えるとか
- やっぱ先人は偉いから Moko < Oyaji って覚えるとか
moko < Oyaji
=> true
moko > Oyaji
=> false
Object < Kernel
=> true
Kernel < BasicObject
=> nil # あれ?????????(後述
Object < BasicObject
=> true
↓こんなゴタクなんか無視してもいいんじゃないのかなーと思っている
継承元ほど、より一般的な性質を持つ(汎化)
↑
↑継承元(スーパークラス: Oyaji)
↓継承先(self: Moko)
↓
継承先ほど、より固有的な性質を持つ(特化)
オブジェクトが持つインスタンスメソッド、インスタンス変数を確認する
オブジェクトが持つインスタンスメソッド
- オブジェクト外から確認する事ができます
- インスタンスメソッドはどこに存在するのかというと、クラスオブジェクトに存在し、
- インスタンス変数はどこに存在するのかというと、インスタンスに存在するのでしたね
クラス#instance_methods | publicメソッドの一覧 + protectedメソッドの一覧 |
クラス#public_instance_methods | publicメソッドの一覧 |
クラス#protected_instance_methods | protectedメソッドの一覧 |
クラス#private_instance_methods | privateメソッドの一覧 |
引数にfalseを指定すると、スーパークラスに実装してあるメソッドは表示されない。自分自身に実装されているものだけを表示する
Oyaji.instance_methods(false)
=> [:method1]
Moko.instance_methods(false)
=> [:method2]
オブジェクトが持つインスタンス変数
オブジェクト#instance_variables
Oyaji.new(1).instance_variables
=> [:@a]
Moko.new(1, 2).instance_variables
=> [:@b, :@a]
メソッドに別名を付ける
- alias式はメソッドではないので引数のように見えるが間にカンマは入れない
- メソッド名は識別子かシンボルで指定する
alias式の構文
alias 新メソッド名 旧メソッド名
alias 新グローバル変数名 旧グローバル変数名
class Hage
def zura1; p 1; end
def zura2; p 2; end
end
Hage.instance_methods(false)
=> [:zura1, :zura2]
class Hage
def zura1; p 1; end
def zura2; p 2; end
alias :zura3 :zura1
end
Hage.instance_methods(false)
=> [:zura1, :zura2, :zura3]
Hage.new.zura3
1
=> 1
メソッドを取り消す
- undef式はメソッドではないので引数のように見えるが間にカンマは入れない・・かと思えば、カンマで複数メソッドを一気に消せるようになっている
- メソッド名は識別子かシンボルで指定する
alias式の構文
undef メソッド名
undef メソッド名, メソッド名
class Hage
def zura1; p 1; end
def zura2; p 2; end
end
Hage.instance_methods(false)
=> [:zura1, :zura2]
class Hage
def zura1; p 1; end
def zura2; p 2; end
undef :zura1
end
Hage.instance_methods(false)
=> [:zura2]
Hage.new.zura1
NoMethodError: undefined method 'zura1'
エイリアスが定義されている片方をundefしても、もう片方でアクセスが可能(まさにエイリアスのハードリンクを作るような感じなのかね?)
class Hage
def zura1; p 1; end
alias :zura2 :zura1
undef :zura1
alias :zura3 :zura2
undef :zura2
end
Hage.instance_methods(false)
=> [:zura3]
Hage.new.zura3
1
=> 1
method_missing
ようこそ黒魔術の世界へ
- 継承チェーンを駆け上った末のBasicObjectでもメソッドが見つからない場合、呼び出し元のオブジェクトの#method_missingが呼び出される
- この#method_missingも継承チェーンを駆け上り、BasicObject#method_missingまで到達すると例外NoMethodErrorが発生する
- 各クラスのmethod_missingメソッドをオーバーライドする事で、継承チェーン全てをチェックしてもメソッドが見つからない場合の動作をフックすることができる
- 第1引数はメソッド名、第2引数以降に指定された引数が渡される
- メソッドが見つからない時って、(今判ってるだけで少なくとも)2回も継承チェーンを駆け上るんだ、へー
↑実は、この本にはここまで書いていない。しかし、こうとしか思えない動作をするのだ。ツッコんで下さいお願いします
いくつか例を出します
- moko#hageメソッドが呼び出されるが、どうやら存在しない
- RubyインタプリタはスーパークラスであるOyajiに同名のメソッドが無いかチェックし、発見、無事実行できた
- Moko#method_missing は動作しなかった
class Oyaji
def hage(*args)
puts "Oyaji called: hage(#{args})"
end
end
class Moko < Oyaji
def method_missing(method_name, *args)
puts "Moko called: #{method_name}(#{args})"
super
end
end
Moko.ancestors
=> [Moko, Oyaji, Object, Kernel, BasicObject]
Moko.new.hage('zura')
Oyaji called: hage(["zura"])
=> nil
- moko#hageメソッドが呼び出されるが、どうやら存在しない
- RubyインタプリタはスーパークラスであるOyajiに同名のメソッドが無いかチェックし、発見できない
- Rubyインタプリタは継承チェーンを駆け上り、Object, Kernel, BasicObjectでも見つからないので moko#method_missing(:hage, *[‘zura’]) を実行する
- Moko#method_missing はメッセージを表示し、スーパークラスであるOyaji#method_missingへ処理を移行する
- RubyインタプリタはスーパークラスであるOyajiに#method_missingが無いかチェックし、発見できない為、スーパークラスであるObject#method_missingへ処理を移行する
- 継承チェーンを駆け上り、BasicObject#method_missingにて 例外NoMethodErrorが発生した
class Oyaji
end
class Moko < Oyaji
def method_missing(method_name, *args)
puts "Moko called: #{method_name}(#{args})"
super
end
end
Moko.ancestors
=> [Moko, Oyaji, Object, Kernel, BasicObject]
Moko.new.hage('zura')
Moko called: hage(["zura"])
NoMethodError: undefined method 'hage'
- moko#hageメソッドが呼び出されるが、どうやら存在しない
- RubyインタプリタはスーパークラスであるOyajiに同名のメソッドが無いかチェックし、発見できない
- Rubyインタプリタは継承チェーンを駆け上り、Object, Kernel, BasicObjectでも見つからないので moko#method_missing(:hage, *[‘zura’]) を実行する
- Moko#method_missing はメッセージを表示し、スーパークラスであるOyaji#method_missingへ処理を移行する
- Oyaji#method_missing はメッセージを表示し、スーパークラスであるObject#method_missingへ処理を移行する
- 継承チェーンを駆け上り、BasicObject#method_missingにて 例外NoMethodErrorが発生した
class Oyaji
def method_missing(method_name, *args)
puts "Oyaji called: #{method_name}(#{args})"
super
end
end
class Moko < Oyaji
def method_missing(method_name, *args)
puts "Moko called: #{method_name}(#{args})"
super
end
end
Moko.ancestors
=> [Moko, Oyaji, Object, Kernel, BasicObject]
Moko.new.hage('zura')
Moko called: hage(["zura"])
Oyaji called: hage(["zura"])
NoMethodError: undefined method 'hage'
- moko#hageメソッドが呼び出されるが、どうやら存在しない
- RubyインタプリタはスーパークラスであるOyajiに同名のメソッドが無いかチェックし、発見できない
- Rubyインタプリタは継承チェーンを駆け上り、Object, Kernel, BasicObjectでも見つからないので moko#method_missing(:hage, *[‘zura’]) を実行する
- Moko#method_missing はメソッド名が :hage であれば文字列を戻り値としmethod_missingの処理は中断する
- 最終的に「hageメソッドが見つからなかった」というエラーは隠蔽され、hageメソッドが見つかったかのように処理は続いていく
class Oyaji
end
class Moko < Oyaji
def method_missing(method_name, *args)
if method_name == :hage
return 'hage-!'
end
super
end
end
Moko.ancestors
=> [Moko, Oyaji, Object, Kernel, BasicObject]
Moko.new.hage('zura')
=> "hage-!"
オープンクラス クラスをオープンする
同名のクラスを複数定義してもエラーにはならない。何が起こっているの?
class Moko
def method1
p '1'
end
end
class Moko
def method2
p '2'
end
end
Moko.new.method1
=> "1"
Moko.new.method2
=> "2"
- class式はクラスオブジェクトを生成する、と上の方に書いたような気もしますが、厳密には
- クラスオブジェクトが存在しない場合は生成する
- クラスオブジェクトが存在する場合にはそのオブジェクトを再度「開いて」評価します
class Moko
def method1
p '1'
end
end
class Moko
alias :method2 :method1
undef :method1
end
Moko.new.method1
NoMethodError: undefined method 'method1'
Moko.new.method2
=> "1"
Moko.instance_methods(false)
=> [:method2]
- このように定義済みのクラスを再定義できるクラスをオープンクラスと呼ぶ
- 一度定義したクラスを再定義の為に開く事をクラスの再オープンと呼ぶ
- 同名のメソッドを定義すると上書きされる
- もちろん標準搭載の組み込みクラスも再オープンできる。当然、ソース全体に影響が及ぶので注意
class String
def hage
'hage-!'
end
end
'moko'.hage
=> "hage-!"
- また、スーパークラスを指定して再オープンする場合はスーパークラスの指定は同じクラスを指定しなければならない
- ・・らしいが
- ぶっちゃけ、再オープン時にはスーパークラスを指定しなきゃいいんじゃねーのとは思う
class Oyaji
end
class Hage
end
class Moko < Oyaji
end
class Moko < Hage
end
TypeError: superclass mismatch for class Moko
class Moko
def method
'ok'
end
end
Moko.new.method
=> "ok"
Mix-in
- Rubyで多重継承を実現する機能
- Rubyでは単一継承のみが許されている。つまり同時に複数のスーパークラスを持つ多重継承は許されていない
- Rubyはクラスに機能を混ぜ合わせる事で複数のクラスで機能を共有する機能を提供している。それがMix-inである
- 複数のクラスで適用したい機能はモジュールというオブジェクトに定義し、それをクラスに取り込む事で機能を拡張する
モジュールとクラスの違い
- モジュールは単独ではインスタンス化できない
- モジュールは継承できない
- モジュールはクラスや他のモジュールに取り込むことができる
モジュールの目的
- 名前空間の提供(後述します
- Mix-inにおける機能定義
モジュールを定義してみる
module Hage
def hage
@a
end
end
クラス定義にそっくり過ぎて笑う
モジュールオブジェクトの確認
Hage.ancestors
=> [Hage]
Hage.instance_methods(false)
=> [:hage]
Hage.new
NoMethodError: undefined method 'new' for Hage:Module
生成したモジュールは Module#include や Module#prepend メソッドなどでクラスに取り込むことができる
class Moko
include Hage
def initialize(a)
@a = a
end
end
Moko.new(1).hage
=> 1 # @aが表示された
モジュールのメソッドの探索経路
継承チェーンを確認してみる
- Mokoオブジェクトは自動的にObjectクラスを継承している筈なのに、間に Hage が入り込んでいる
- でも、Moko#superclassはObjectのまま
- そしてMokoクラスにはhageメソッドが存在しない・・これは一体?
Moko.ancestors
=> [Moko, Hage, Object, Kernel, BasicObject]
Moko.superclass
=> Object
Moko.instance_methods(false)
=> []
- Rubyインタプリタはincludeが実行されると、指定されたモジュールに対応する無名クラスを作成し、スーパークラスとの間に組み入れる
- 別の言い方をすれば、Mokoインスタンス内でメソッドが見つからない場合は継承チェーンを駆け上り、今回includeした無名クラス内にメソッドが定義されていないか確認、見つからなければ更に上位、Objectクラス内に存在するかチェックする、という、コレまで説明してきたものと同じ動作をする
- この無名クラスはインタプリタの実装の都合で作成されるものであり、ユーザがその存在をする必要はないのでsuperclass等で参照できないようになっている(優しさ)
複数モジュールをインクルードしたら継承チェーンはどうなるんすか
- includeは自分のすぐ直上に無名クラスを追加する
- つまり
- 1回目のincludeで自分(Moko)とObjectの間にMoko1が追加され、
- 2回目のincludeで自分(Moko)とMoko1の間にMoko2が追加され、ancestorsの結果となる
- つまり
- Moko1とMoko2に同名のメソッドが定義してあった場合、後からincludeした方が優先的にメソッド探索対象となるという事である
- さらにMokoに同名のメソッドが定義してあった場合、includeの順番等は関係なく、自らのメソッドが一番優先されるメソッド探索対象である事は何も変わらない。自らにそのメソッドが定義されていない場合に初めて継承チェーンに追加されたメソッドが探索対象になるという事を忘れてはならない
module Moko1
end
module Moko2
end
class Moko
include Moko1
include Moko2
end
Moko.ancestors
=> [Moko, Moko2, Moko1, Object, Kernel, BasicObject]
Module#prependメソッド
- 2.0から新たに追加された
- includeと同様にモジュールをmix-inしてくれる
- includeは includeを呼び出したクラスにそのメソッドがない場合に初めてmix-inした無名クラスに対してメソッド探索が行われるが
- prependは prependはを呼び出したクラスよりも先にmix-inしたモジュールに対してメソッド探索を行う
- つまり、同名のメソッドがあった場合、クラスに定義されているものよりもモジュールに定義してあるメソッドの方が優先されるという事
includeの場合
module Hage
def zura1
true
end
def zura2
true
end
end
class Moko
def zura1
false
end
include Hage
def zura2
false
end
end
Moko.ancestors
=> [Moko, Hage, Object, Kernel, BasicObject]
Moko.new.zura1
=> false
Moko.new.zura2
=> false
prependの場合
module Hage
def zura1
true
end
def zura2
true
end
end
class Moko
def zura1
false
end
prepend Hage
def zura2
false
end
end
Moko.ancestors
=> [Hage, Moko, Object, Kernel, BasicObject]
Moko.new.zura1
=> true
Moko.new.zura2
=> true
でも、prependって何に使うの?
- 何も考えずにprependすると、モジュール内のメソッドを先に実行しクラスに定義してあるインスタンスメソッドを無視する、ような動きになるが
- prependしたモジュール内の同名メソッドの処理を行ってからsuperを使って元のメソッドの処理を行う事ができるようになる
- 元のクラスのインスタンスメソッドの振る舞いを変えずに、前処理を変更できる、ような感じだろうか
class Moko
def initialize(hage)
@hage = hage
end
def hage?
@hage ? 'hage-!' : ''
end
end
Moko.new(false).hage?
=> ""
Moko.new(true).hage?
=> "hage-!"
module Hage
def hage?
@hage = true # prependされたHage#hage?が@hageをtrueに変更してからMoko.hage?が呼ばれることになる。必ずハゲ
super
end
end
class Moko
prepend Hage
end
Moko.new(false).hage?
=> "hage-!"
Moko.new(true).hage?
=> "hage-!"
includeとprepend両方ともされた場合、早いもん勝ち
module M1
end
module M2
include M1
end
M2.ancestors
=> [M2, M1]
module M2
prepend M1
end
M2.ancestors
=> [M2, M1]
module M3
prepend M1
end
M3.ancestors
=> [M1, M3]
module M3
include M1
end
M3.ancestors
=> [M1, M3]
特異クラス
- 指定したインスタンスだけに適用される特別なクラス
- 無名クラスとは別物
特異クラスの性質
- 「特定のインスタンス(オブジェクト)だけに特別な性質を持たせたい場面がよく発生します」
-
本当だろうか?私は発生した記憶がない
- 例えば、Mokoクラスからインスタンス化された moko1, moko2 の内、moko2 だけにメソッドを追加したいなら
- Mokoを継承したExtMokoクラスを新たに定義しメソッドを追加して、moko1はMokoクラスから、moko2はExtMokoからインスタンス化すればよい
- が、いちいち継承するクラスを定義するのはめんどくさい
こんな時、Rubyではインスタンスに直接メソッドを定義する事ができる。このメソッドを特異メソッドと呼ぶ
特異クラスの前に特異メソッド
特異メソッドの定義
def <オブジェクト名>.<新メソッド名>
....
end
実際のオブジェクトに対して特異メソッドを定義してみる。片方のオブジェクトには新たにメソッドが定義されて、もう片方には存在していない
class Moko
end
moko1 = Moko.new
moko2 = Moko.new
def moko1.hage
true
end
moko1.hage
=> true
moko2.hage
NoMethodError: undefined method 'hage'
a = 'a'
b = 'b'
def b.<=>(other)
raise NoMethodError
end
a <=> b
=> -1
b <=> a
NoMethodError: NoMethodError
- IntegerオブジェクトやSymbollオブジェクト等には特異メソッドは定義できない
- 「特異メソッドのレシーバとなるオブジェクトは、そのアイデンティティ(同一性)が重要です。Fixnumのような値オブジェクトでは、同一性は重要ではないので、特異メソッドを定義するのは無意味」
- 全ての1は同じオブジェクト、全ての:mokoは同じオブジェクト、みたいなのには特異メソッドは定義できない、って覚えればいいよ
a = 1
def a.fa
end
TypeError: cant define singleton
さて、この特異メソッドはどういう仕組で実現されているのか
ここに図が必要だよなー
- クラスがモジュールをincludeした際には、自らとスーパークラスの間に新たなクラス(無名クラス)を追加していた
- オブジェクトが特異メソッドを追加する時には、自らと自分の生成元のクラスの間に新たなクラス(特異クラス)を追加する
- この特異クラスはインタプリタの実装の都合で作成されるものであり、ユーザがその存在をする必要はないのでclass等で参照できないようになっている(優しさ)
特異クラスの参照と再オープン
すぐ上で「参照できないようになっている」と書いたが、方法が無いわけでもない。特殊な構文を使用する事で可能となる
class Moko
end
moko = Moko.new
def moko.hage
true
end
singleton_class = class << moko
self
end
singleton_class
=> #<Class:#<Moko:0x007f8d4e22ff28>>
特異クラスの再オープン式
これって「classにクラスメソッド追加するあの書き方」じゃん!
class << 対象オブジェクト
end
- クラスが再オープンできるなら、メソッドの再定義も もちろん出来るわけですよ!
- つまり、特異メソッドの定義方法その2
class << moko
def zura?
true
end
end
moko.zura?
=> true
さて、何が行われているのか?の前にselfとメソッドの定義先をもう一度見てみる
selfの参照先
- 自分自身を示す特別なオブジェクト
- 使われる場所で変わる。クラス内部で使えばそのクラス、インスタンスメソッド定義内で使えばそのオブジェクトを指す
- ちなみにメソッドが実行されるオブジェクト(クラスもオブジェクトだぞ)をレシーバと呼ぶ
class Moko
p self # => Moko # Mokoクラスそのものが出力される
def method
self
end
end
moko = Moko.new
moko == moko.method
=> true # moko.method は mokoそのものを返す
メソッドの定義先
- メソッドがネストしたらどうなる
- 外側のメソッドが評価された時に、そのdefが書いてある場所 self == Moko に定義される
- 外側のメソッドが実行された時に、外側のメソッドが定義されているクラスに内側のメソッドが追加される
- 内側のメソッドはMokoクラスのインスタンスメソッドとなる
class Moko
def method1
def method2
end
end
end
Moko.instance_methods(false)
=> [:method1]
Moko.new.method1
Moko.instance_methods(false)
=> [:method1, :method2]
特異クラスの再オープン再び
ちょっと前に出てきたソースの説明をもう一度します
- クラス定義(再オープンでも可)内でメソッドを定義するとそのクラスのインスタンスメソッドとなる
- つまり
- 特異クラスの再オープン内でメソッドを追加するとその特異クラスのインスタンスメソッドとなるという事
class Moko
end
moko = Moko.new
class << moko
def zura?
true
end
end
moko.zura?
頭がこんがらがってきたか?俺もだ
extendメソッド
- クラスにモジュールをMix-inするにはincludeを使うんでしたね
- という事は、特異クラスにもモジュールはMix-inできるの? => もちろんです
moko1 = Moko.new
class << moko1
include Hage
end
moko1.method
# ....
- ↑上の例では、moko1オブジェクトの特異クラスを再オープンしてMix-inを行っている
- 当然の事だが、他のMokoクラスのオブジェクトには影響がない。moko1オブジェクトのみがその特異クラスを参照しているからである
-
でも、もっと簡単に表記できないの? => できます
- そこで出てくるのがextendさんです
- 上のコード例と同じ動作(特異クラスへのMix-in)です。でも簡潔に書けますね
moko1 = Moko.new
moko1.extend(Hage)
moko1.method
# ....
- 蛇足
- 「オブジェクトに対してextendできるんなら、クラスに対してextendしたら・・・」その通りです。そのうち出てきますよ
Refinements
- Rubyでは既存のクラスにメソッドを任意に変更したり追加したり自由なのだが、
- 自由すぎて全体が影響を与える為、変更に対する影響度を予測しづらいという問題もある
-
その解決方法として 2.1 からRefinementsという仕組みが導入されました
- refine メソッドで変更を加えるクラスを宣言し
- using メソッドを呼び出した以降から変更が有効になる というもの
- usingの有効範囲は↓
- クラス・モジュール定義外で呼び出した時にはファイル末尾まで
- クラス・モジュール定義内で呼び出した時にはクラス・モジュール定義の末尾まで
Refinementsはまだ歴史も浅く、仕様が枯れるのはいつなのか分かりません。試験に出題される際には基本的な部分を問われるくらいらしい
module Haging
refine String do
def hage
'hage-!'
end
end
end
class Moko
def method
'moko'.hage
end
end
Moko.new.method
NoMethodError: undefined method 'hage' for "moko":String
class Moko
using Haging
def method
'moko'.hage
end
end
Moko.new.method
=> "hage-!"
クラスメソッド
- これまで、メソッド探索はMix-in(include, prependの無名クラス)と特異クラスを見てきました
- Moko.new みたいな、クラスがレシーバになってるメソッドがクラスメソッドです
- Rubyではクラスメソッドをクラスに対する特異特異クラスのメソッドとして定義します
これまで クラス と呼んできたモノの招待を見ていきます
Classクラス
- これまでクラスと呼んできたモノはClassクラスのオブジェクト
- Classクラスはクラスを定義するクラス
↓こいつがClassクラスです
String.class
=> Class
- Classクラスのインスタンスを作る == クラスを定義する事ができる
- クラスは単にClassクラスのインスタンスなので、普通にClassクラスのnewメソッドでクラスを定義することができる
Moko = Class.new
Moko.superclass
=> Object
Moko.new
=> #<Moko:0x007fbd331feaf8>
Classクラスのnewメソッドの引数でスーパークラス、ブロックでメソッド定義を指定できる
class Oyaji
def initialize(a)
@a = a
end
def method1
@a
end
end
Moko = Class.new(Oyaji) do
def initialize(a, b)
@b = b
super(a)
end
def method2(c)
@a + @b + c
end
end
Moko.new(1, 2).method1
=> 1
Moko.new(1, 2).method2(3)
=> 6
- Classクラスは、クラス(クラスオブジェクト)よりも更に抽象度を上げたモノとなる。
- Classクラスのレイヤとクラス(クラスオブジェクト)のレイヤとインスタンスのレイヤを分けると次のような感じだろうか
-
さらにClassクラスのスーパークラスはModuleクラスだったりします。こんがらがってきましたね
- memo
- クラス(クラスオブジェクト)がClassクラスのインスタンスなように、
- モジュールはModuleクラスのインスタンスです
Classクラス界 (さらに抽象的方面)
Class => superclass => Module => superclass => Object
----------------
Classオブジェクト界 (抽象的方面)
Moko => superclass => Oyaji
----------------
インスタンス(オブジェクト)界 (具体的方面)
moko1
moko2
クラスメソッドの定義
おそらくまだ混乱していると思いますが、クラスメソッドを見ていきましょう
- インスタンスメソッド(インスタンス達が共通して使うメソッド)は抽象度を1つ上げたクラス(クラスオブジェクト)に定義しました
- クラスメソッドも同じように抽象度を1つ上げた、Classクラスを再オープンしてメソッドを定義できます
- このやり方だと、生成したインスタンス全てで有効となります。つまり、全てのクラスで有効になるという事です やべえ
クラスメソッドの定義 その1 (全てのクラスで有効)
class Class
def c_method1
'hage-!'
end
end
String.c_method1
=> "hage-!"
Integer.c_method1
=> "hage-!"
- 上記の方法だと全てのクラスで有効なクラスメソッドになるが、それでは1つのクラスのみにクラスメソッドを定義するにはどうしたらよいか
- 何度も言っているように、クラス(クラスオブジェクト)はClassクラスのインスタンスです
-
「複数個生成できるインスタンスの中の、たった一つだけに特別な性質を与える」のは特異クラスの出番です
- 特異クラスのオープン式やメソッド定義には対象としてインスタンスを(当然)指定してきました
- そこにクラスオブジェクトを指定するだけなのです
クラスメソッドの定義 その2
# オブジェクトに特異メソッドを定義する時にはこう書いていた
class Moko
end
moko = Moko.new
def moko.method
'hage-!'
end
moko.method
=> "hage-!"
同様に、オブジェクトの代わりにクラス(クラスオブジェクト)を指定するだけ
class Moko
end
def Moko.method
'hage-!'
end
Moko.method
=> "hage-!"
クラスメソッドの定義 その3
class Moko
def Moko.method
'hage-!'
end
end
Moko.method
=> "hage-!"
クラスメソッドの定義 その4
class Moko
def self.method # クラス定義内ではselfがMokoを指す
'hage-!'
end
end
Moko.method
=> "hage-!"
クラスメソッドの定義 その5 (特異クラスの再オープン)
class Moko
class << self
def method
'hage-!'
end
end
end
Moko.method
=> "hage-!"
クラスメソッドの定義とインスタンスへの特異メソッドの定義の違い
- 考え方は同じモノだと言ったが、違う点がある
- クラスオブジェクトへのクラスメソッド定義で作成された特異メソッドも、クラスオブジェクトの継承チェーンのように特異メソッド同士で継承チェーンを保持する
- これはクラスメソッドを継承する為である。
- ありあえず「クラスメソッドも継承される」と覚えておけばおk
(このあたりにメモ画像を貼らないと、たぶん伝わらない、ごめんなさい)
class Moko
TOKUI_KURASU = class << self
self
end
end
class Object
TOKUI_KURASU = class << self
self
end
end
Moko::TOKUI_KURASU.superclass == Object::TOKUI_KURASU
=> true
Moko::TOKUI_KURASU.ancestors
=> [#<Class:Moko>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
Moko::TOKUI_KURASU.ancestors[1] == Object::TOKUI_KURASU
=> true
extendでクラスメソッドを定義する
extendはレシーバに特異メソッドを追加するのでクラスオブジェクトの特異メソッド==クラスメソッドとして定義される
class Moko
end
module KURASU_METHOD
def method
'hage-!'
end
end
Moko.extend(KURASU_METHOD)
Moko.method
=> "hage-!"
おつかれさま!これでRubyのメソッド探索経路の話は終わりですよ!
メソッドの可視性
- publicなメソッドはどのインスタンスからも実行できる
- protectedなメソッドは自分自身か、サブクラスのインスタンスからしか実行できない
- privateなメソッドはレシーバを付けた呼び出しができない
デフォルトはpublicです
class Moko
def public_method1; 1 ; end # デフォルトはpublicメソッドになる
public # 以降はpublicメソッドになる
def public_method2; 2 ; end
protected # 以降はprotectedメソッドになる
def protected_method3; 3 ; end
def protected_method4; 4 ; end # 同じくprotectedメソッドになる
private # 以降はprivateメソッドになる
def private_method5; 5 ; end
end
Moko.new.public_method1
=> 1
Moko.new.public_method2
=> 2
Moko.new.protected_method3
=> NoMethodError: protected method 'protected_method3' called
Moko.new.protected_method4
=> NoMethodError: protected method 'protected_method4' called
Moko.new.private_method5
=> NoMethodError: private method 'private_method5' called
メソッド名を指定して可視性を変更する
各可視性変更メソッドへメソッド名を指定する事で、後から可視性を変更する事もできる
class Moko
def public_method1; 1 ; end # デフォルトはpublicメソッドになる
def public_method2; 2 ; end
def protected_method3; 3 ; end
def protected_method4; 4 ; end
def private_method5; 5 ; end
public :public_method1, :public_method2 # 指定したメソッドがpublicになる(と言ってもデフォルトでpublicだが)
protected :protected_method3, :protected_method4 # 指定したメソッドがprotectedになる
private :private_method5 # 指定したメソッドがprivateになる
end
Moko.new.public_method1
=> 1
Moko.new.public_method2
=> 2
Moko.new.protected_method3
=> NoMethodError: protected method 'protected_method3' called
Moko.new.protected_method4
=> NoMethodError: protected method 'protected_method4' called
Moko.new.private_method5
=> NoMethodError: private method 'private_method5' called
privateの振る舞い
- protectedは使いどころがかなり限られますが、privateは結構使います
- 同じオブジェクトに定義されているメソッドからしか呼び出せない、と言うよりは言葉通り、レシーバを指定した呼び出しができない
- 同じオブジェクトに定義されているメソッドであっても、レシーバを指定するとエラーになる
class Moko
def public_method1
private_method
end
def public_method2
self.private_method # レシーバを指定した呼び出し
end
private
def private_method
'hage-!'
end
end
Moko.new.public_method1
=> "hage-!"
Moko.new.public_method2
NoMethodError: private method 'private_method' called
サブクラスにおける可視性の変更
メソッドの可視性はクラスに結び付けられているので、サブクラスで自由に変更することができる
class Oyaji
private
def private_method
'hage-!'
end
end
# Oyajiクラスに定義されているprivate_methodはプライベートなので呼び出せない
Oyaji.new.private_method
=> NoMethodError: private method 'private_method' called
# 継承した直後に可視性を変更する
class Moko < Oyaji
public :private_method
end
Moko.new.private_method
=> "hage-!"
# もちろんOyajiクラスを再オープンして可視性を変更してもいいんですけどね
class Oyaji
public :private_method
end
Oyaji.new.private_method
=> "hage-!"
メモ
- ここは出題範囲ではない
- Moduleのスーパークラスはひとつ下のレイヤにあるObject
- Objectクラスのメソッドは、インスタンスメソッドとしてもクラスメソッドとしても使えることを意味します
- (分からんのでメモっておく)
Kernelモジュール
- たまに出てきた彼は何者なのか
- putsやpのような組み込み関数はKernelモジュールのメソッドである
-
ちなみに組み込み関数と言っているが結局はメソッドである
- レシーバを省略してpメソッドを呼び出す
- pメソッドを探してObject.pが呼び出される(存在しない)
- ObjectクラスオブジェクトはKernelモジュールをincludeしているのでKernel.pが呼び出される
-
Objectクラスは全てのクラスのスーパークラスに存在するのでどこからでも呼び出す事ができる
- Kernelモジュールの多くのメソッドはprivateが指定されているのでレシーバを省略しないと呼び出せない
# 呼び出せるやんけ・・・どうしてや
Kernel.p 'hage-!'
"hage-!"
独自の組み込み関数の定義(Kernelを拡張)
- Rubyの組み込み関数と同等のものを定義したい場合にはKernelモジュールにprivateメソッドを定義する事ができる
module Kernel
private
def hage
'hage-!'
end
end
hage
"hage-!"
self.hage
NoMethodError: private method 'hage'
独自の組み込み関数の定義(Objectを拡張)
- でも、特に理由がない限りはKernelではなくObjectにprivateメソッドを追加した方が良いとか。同等の効果
class Object
private
def hage
'hage-!'
end
end
hage
"hage-!"
self.hage
NoMethodError: private method 'hage'
アクセッサメソッド
インスタンス変数へアクセスする専用のメソッドみたいな 変数、定数、予約語、演算子関係
class Moko
def hage
return @hage
end
def hage=(value)
@hage = value
end
end
moko = Moko.new
moko.hage
=> nil
moko.hage = 'hage-!'
=> 'hage-!'
moko.hage
=> 'hage-!'
値を取り出すメソッドをゲッター(getter)、値をセットするメソッドをsetter(セッター)を言います
アクセッサメソッドの生成を行うメソッド
でも、インスタンス変数の数だけゲッターとセッターを書くのはめんどくさいので、アクセッサ生成用のクラスメソッドが用意されている
attr_reader | getterメソッドを生成する |
attr_writer | setterメソッドを生成する |
attr_accessor | getterとsetterメソッドを生成する |
attr | 1.8以前ではこう書いていた。getterメソッドを生成する、第二引数にtrueを指定するとsetterメソッドも生成する |
class Moko
attr_accessor :hage
def test
@hage
end
end
moko = Moko.new
moko.hage
=> nil
moko.hage = 'hage-!'
=> "hage-!"
moko.hage
=> "hage-!"
moko.test
=> "hage-!"
インスタンス変数は継承されないが、メソッドは継承されるのでサブクラスからアクセッサを経由してスーパークラスのインスタンス変数にアクセスすることが可能・・・って、マジで?
スーパークラスのメソッドを使うけど、アクセスするのは自分自身のインスタンス変数なんじゃないの?
これ、この本を書いた人がトチ狂ったんじゃないかと思ってるんだけど どうなの?
class Moko2 < Moko
end
moko2 = Moko2.new
moko2.instance_variables
#=> []
moko2.hage
#=> nil
moko2.hage = 'hage-!'
#=> "hage-!"
moko2.hage
#=> "hage-!"
moko2.instance_variables
#=> [:@hage]
ネストしたスコープ
クラス式やモジュール式は定数にクラスオブジェクト、モジュールオブジェクトが格納されますが、:: 演算子を使ってネストを指定できる
module A1
end
A1::B = 1
A1::B
=> 1
module A2
B = 1
end
A2::B
=> 1
この例はエラーになる A3::Bは1(数値オブジェクト)になる為。::はクラスオブジェクトかモジュールオブジェクトにしか存在しない
module A3
B = 1
end
A3::B::C
#=> TypeError: 1 is not a class/module
こうするべきですかね
module A4
module B
C = 1
end
end
A4::B::C
=> 1
トップレベルで定義した定数はObjectクラスの定数、内部で定義した初期化されたクラス、モジュールに配置される
A5 = 'A5'
class A6
A7 = 'A7'
end
Object.constants
=> [... :A5, :A6, ...]
A6.constants
=> [:A7]
ネストした定数に相対位置でアクセス
module A8
module B2
C1 = 'C1'
end
p B2::C1
#=> "C1"
end
前頭に :: を書く事で、ネストした定数に絶対位置でアクセス
module A9
module B2
C1 = 'C1'
p ::A9::B2::C1
#=> "C1"
end
end
定数が見つからない場合はネストの外側に向かって順に探索される
module A9
Z1 = 'Z1_1'
module B3
Z1 = 'Z1_2'
module C3
p Z1
end
end
end
=> "Z1_2"
module A10
Z1 = 'Z1_1'
module B3
module C3
p Z1
end
end
end
=> "Z1_1"
スコープの外側でも定数が見つからない場合はスーパークラスで定義された変数やincludeされたモジュールまでも、継承チェーンを掛け登って探索する
探索と言えば、missingです。定数にもconst_missingがあります
# 定数が見つからない場合、1を返す
module Hage
def self.const_missing(id)
p id
1
end
end
Hage::FDASFADSFDS
#=> :FDASFADSFDS
#=> 1
- こんな感じが定数のスコープでした。実際のプログラミングではクラスの名前空間の管理に使います
- 意味や目的毎にモジュールを分けてクラスを定義してわかりやすくしようぜってこった