動的言語としてのRubyの特徴を抑えておく。
◆ 動的ディスパッチ
実行時にメソッドを定義する。
class MyClass
def my_method(arg)
arg * 2
end
end
MyClass.new.my_method(10) # => 20
MyClass.new.send(:my_method, 10) # => 20
普通にメソッドを利用する場合も、sendを使ってメソッドを
呼ぶのも同じ結果を得るのだが、後者に何かメリットはあるのだろうか。
send()を使えば、呼び出すメソッドはただの引数となり、
コードの実行時にメソッドを動的に選択することができるのである。
※':文字列'はただのシンボル。
send()はパターンディスパッチにも使われる。
パターンディスパッチによって
メソッド名のパターンに基づいてメソッドを振り分けることが可能である。
class MyClass
def my_first_method()
puts "first"
end
def my_second_method()
puts "second"
end
end
obj = MyClass.new
obj.methods.each do |m|
obj.send(m) if m.to_s =~ /^my_/
end
◆ 動的メソッド
その場でメソッドを定義する。
class MyClass
define_method :my_method do |arg|
arg * 2
end
end
MyClass.new.my_method(10) # => 20
他の例も示そう。
同じようなインスタンスメソッドを書かなくてはならないことがある。
class MyClass
def success
@state = :success
end
def error
@state = :error
end
end
動的メソッドを使えば重複を取り除ける。
class MyClass
[:success, :error].each do | method |
define_method method do
@state = method
end
end
end
class MyClass
def self.states(*args)
args.each do |arg|
define_method arg do
@state = arg
end
end
end
states :success, :error
end
◆ ゴーストメソッド
定義していないメソッドに応答する。
class MyClass
def method_missing(name, *args)
args[0] * 2
end
end
MyClass.new.my_method(10) # => 20
もう少し実用的な例として、
ゴーストメソッドを使ってセッターとゲッターを定義する。
class MyClass
def initialize
@attributes = {}
end
def method_missing(name, *args)
attribute = name.to_s
if attribute =~ /=$/
@attributes[attribute.chop] = args[0]
else
@attributes[attribute]
end
end
end
MyClass.new.name = "shinya"
◆ 遅延評価
Procやlambdaにコードとコンテキストを保管して後で評価する。
obj = Proc.new {|x, y| x * y} # もしくは
obj = lambda {|x, y| x * y}
obj.call(10, 20)
class MyClass
def store(&block)
@my_code = block
end
def execute(arg1, arg2)
@my_code.call(arg1, arg2)
end
end
obj = MyClass.new
obj.store {|x, y| x * y}
obj.execute(10, 20) # => 200
◆ selfについて
突然だが、Rubyの基礎としてselfをおさえておく。
selfの理解が曖昧だとRubyの黒魔術を理解しきれない点があるためである。
メソッドが呼び出された時のオブジェクトをレシーバという。
メソッドを呼び出すとそのレシーバがselfになる。
selfのことをカレントオブジェクトとも呼ぶ。
さて、メソッド内でさらにメソッドを呼び出すコードを書いてみる。
どちらのprintメソッドが呼ばれるか分かるだろうか。
継承チェーンとしてはincludeの順に従いこうなる。
モジュールをインクルードするとそのクラスの真上の継承チェーンに
モジュールが挿入される。
Projector
↑
Copier
↑
book → Book
module Projector
def print
puts "projector"
end
end
module Copier
def print
puts "copier"
end
def takeout
end
end
class Book
include Projector
include Copier
end
Book.new.takeout # >= copier
直感と反した、と感じる人も多いだろう。
メソッドを明示せずに呼び出すメソッドはすべてselfに対しての呼び出しとなり、
再度継承チェーンをたどっていくのである。
一方、クラスやモジュールの定義の中では、selfは定義されたクラスやモジュールになる。
class MyClass
self # => MyClass
end
カレントオブジェクトのことをselfと呼ぶのではないか、
と疑問を持ったかもしれない。
思い出してほしい。
クラスはClassクラスのインスタンスである。
クラスもオブジェクトにすぎない。
◆ スコープ1
スコープはclass, module, defといったスコープゲートで区切られている。
大事なところなのでもう一度。
スコープはclass, module, defといったスコープゲートで区切られている。
トップレベルドメインで定義された変数を
クラスの中で利用するにはどうすればいいか。
my_var = "test"
・classのスケープゴートの飛び越え
classをスコープゲートではない何かに置き換えればよい。
MyClass = Class.new() do
my_var #参照可能になる
def my_method
my_var # ただし、この中では参照できない
end
end
Class.new()がclassの置き換えになり、
classのスコープゲートを飛び越えることができる。
Class.new()とは何であろうか。
クラスはClassクラスのインスタンスにすぎない。
Classクラスからクラスオブジェクトを作ると考えればよい。
Class.new()は引数にスーパークラスを指定できる。
MyClass = Class.new(Array) do
end
以下と同じである。
class MyClass < Array
end
・defのスケープゴートの飛び越え
動的メソッドで説明したdefine_methodを使う。
MyClass = Class.new() do
my_var #参照可能
define_method :my_method do
my_var #参照可能
end
end
また、sendを使ってもいいだろう。
MyClass = Class.new() do
my_var #参照可能
send :define_method, :my_method do
puts my_var #参照可能
end
end
MyClass.new.my_method() でmy_varが見えるだろう。
ちなみに、クラス外でdefin_methodを使う場合は
以下のようにしないといけない。
Kernel.send :define_method, :my_method do
puts my_var
end
トップレベルではObjectをクラスとするmainオブジェクトの中にいるため、
プライベートメソッドであるdefine_methodは呼び出せないのである。
sendはプライベートメソッドも呼び出せる(Object.sendでもいける)。
self # => main
self.class # => Object
・moduleのスケープゴートの飛び越え
moduleに関しても、Class.new()と同様にModule.new()を利用すればよい。
◆ スコープ2
スコープを破るよく使う別の例についてもまとめておく。
・instance_eval()
オブジェクトのコンテキストでブロックを評価することができる。
class MyClass
def initialize
@my_var = 1
end
end
my_var = 2
my_obj = MyClass.new
my_obj.instance_eval do
@my_var # >= 1
my_var # >= 2
end
フラットスコープになるため、ローカル変数にも
アクセスできるようになる。
instance_exec()を使うと引数を渡すことができる。
my_obj.instance_exec(10) do |arg|
@my_var + arg # => 11
end
・class_eval()
既存のクラスのコンテキストでブロックを評価することができる。
class MyClass; end
MyClass.class_eval do
def my_method; end
end
MyClass.new.my_method
instance_eval()はselfに変更を加えるだけだが、
class_eval()はカレントクラスにも変更を加えられる。
class_eval()もフラットスコープを持つ。
◆ 特異メソッド
特定のオブジェクトだけにメソッドを追加することができる。
str = "test"
def str.check()
self == "test" # => true
end
Stringクラスのほかのメソッドには影響はない。
◆ 特異メソッドとクラスメソッドの関係性
まずは当たり前のように使うことのあるクラスメソッドである。
class MyClass
def MyClass.my_method; end
end
MyClass.my_method
リファクタリングのしやすさからselfを使うことが普通だろうか。
class MyClass
def self.my_method; end
end
ここに特異メソッドとの関係性をうかがうことができる。
クラスはclassクラスのオブジェクトにすぎないので、
クラスメソッドはクラスというオブジェクトの
特異メソッドであるのである。
◆ 特異クラス
特異クラスとは特異メソッドを実装するために使われるクラスである。
特異メソッドについては説明した通り、
特定のオブジェクトだけに有効なメソッドのことである。
では、オブジェクトの特異メソッドはクラス内のどこで
定義されるのであろうか。
実は通常のクラスの裏に、特別なクラスが存在している。
これが特異クラスと呼ばれ、特異メソッドはここに住んでいる。
通常は見えない特異クラスはどのようにオープンすればいいか。
特別な構文が存在する。
<<を使うと特異クラスのスコープに入り込むことができる。
str = "test"
def str.check()
self == "test" # => true
end
↓
class << str
def check()
self == "test" # => true
end
end
クラスメソッドの例も示そう。
class MyClass
def self.my_method; end
end
↓
class MyClass
class << self
def my_method; end
end
end
MyClass.my_method
◆ モジュール取り込み
module MyModule
def my_method; end
end
class MyClass
include MyModule
end
MyClass.new.my_method
オブジェクトを作成後は、
インスタンスメソッドmy_methodが利用できるようになる。
では、MyClassのオブジェクトが利用するインスタンスメソッドではなく、
クラスメソッドとして利用するにはどうすればいいだろうか。
MyClass.my_method
として使いたい場合はどうすればいいか、ということである。
class MyClass
class << self
include MyModule
end
end
クラスメソッドを特異クラスで作った場合と同じである。
オブジェクト拡張も同じ理屈である。
module MyModule
def check()
self == "test"
end
end
str = "test"
class << str
include MyModule
end
str.check() # => true
クラスメソッドとしてモジュールを利用する場合は、
普通はincludeではなく、extendを用いるだろうか。
ただし、やっていることは上記の通りである。
class MyClass
extend MyModule
end
MyClass.my_method
◆ フックメソッド
イベントをキャッチできるメソッドがある。
継承時のinheritedや、モジュールのミックスイン時の
includedなどである。
class String
def self.inherited(subclass)
puts subclass
end
end
class MyString < String; end # => MyString
Stringクラスのクラスメソッドとしてinheritedが
自動で呼び出される。
引数のsubclassはMyStringになる。
もう一つ例をあげてみる。
module MyModule
def self.included(includer)
includer.extend(MyMethods)
end
module MyMethods
def my_method
"this is class method"
end
end
end
class MyClass
include MyModule
end
MyClass.my_method # => "this is class method"
MyClassのクラスメソッドとしてincludedが呼び出される。
引数のincluderはMyClassを指す。
そこから、extendしてクラスメソッドを定義する。
フックメソッドを簡易的に自作してみる。
クラスメソッドとしてincludeを用意し、
実際のincludeをオーバライドさせ、
そこからsuperで元のincludeを呼び出す。
super呼び出し時は引数もそのまま引き継がれる。
module MyModule1; end
module MyModule2; end
class MyClass
def self.include(*modules)
super
end
include MyModule1, MyModule2
end
◆ アラウンドエイリアス
メソッドにエイリアスをつけて再定義する。
String#length()メソッドを利用時に、
処理時間を埋め込むようにラップをする。
class String
alias :old_length :length
def length
start = Time.now
result = old_length
time_taken = Time.now - start
puts "length took #{time_taken} seconds"
result
end
end
"abc".length # => length took 6.0e-06 seconds
"abc".old_length # => 3
新しいメソッドを定義しても、
古いメソッドもエイリアスを使って呼び出せる点がポイントである。