メソッド定義
Rubyのオブジェクトはメッセージに反応する。つまりオブジェクトがメッセージを受けると、オブジェクトは対応するメソッドを見つけてその結果を返す。
Rubyではオブジェクト自身はメソッドを持っていない。だからオブジェクトは自身が属するクラスにアクセスして、対応するメソッドを得てその結果を返す。
つまりRubyのメソッドはクラスに定義される。
メソッド定義はdef文で行う。
class Person
def name(arg)
"My name is #{arg}"
end
end
my = Person.new
my.name "Charlie" # => "My name is Charlie"
特定のクラスで定義されたメソッドは、そのクラスから生成されるオブジェクトで使えるようになる。
RubyではすべてのクラスはClassクラスから生成されたオブジェクトである。だからClassクラスで定義されたメソッドは、すべてのクラスで使えるようになる。
class Class
def mother
"My mother is #{self.class}"
end
end
class Person
p mother
end
# >> "My mother is Class"
もちろんClassクラスでも!
class Class
p mother
end
# >> "My mother is Class"
singletonクラスでのメソッド定義
通常一つのクラスからは複数のオブジェクトが生成されるので、それらのオブジェクトは同じメソッドを共有することになる。だけど、特定のオブジェクトだけが使えるメソッドを定義したい場合もある。
そのためにRubyではsingletonクラス(特異クラス)が用意されている。
my = Person.new
his = Person.new
class << my
def secret
"My account number is #100789"
end
end
my.secret # => "My account number is #100789"
his.secret # => 15: undefined method `secret' for #<Person:0x23168> (NoMethodError)
略記法もある1
my = Person.new
def my.secret #myのselfメソッドにする
"My account number is #100789"
end
my.secret # => "My account number is #100789"
前にも書いたようにRubyではクラスもオブジェクトだから、クラスに対してもsingletonクラスを用意して そこでメソッドを定義できる。このsingletonクラスで定義されるメソッドは、そのクラスだけが使えるメソッドになる。
class Person
end
class << Person
def secret
"My secret code is #{self.to_s.reverse}."
end
end
Person.secret # => "My secret code is nosreP."
略記法のほうが広く使われている。
class Person
def self.secret
"My secret code is #{self.to_s.reverse}."
end
end
Person.secret # => "My secret code is nosreP."
このようなクラスが使うメソッドは、一般にクラスメソッドと呼ばれるけれど、機能上普通のオブジェクトが使うメソッドと変わりはない。もちろんClassクラスもオブジェクトだから、singletonクラスを定義できる。
class Class
def self.secret
"My secret code is #{self.to_s.reverse}."
end
end
Class.secret # => "My secret code is ssalC."
モジュールにおけるメソッド定義
Rubyにはモジュールというクラスに似たオブジェクトがある。モジュールはクラスと違ってオブジェクトを生成できないけれども、そこにメソッドを定義することはできる。特定のクラスに特定のモジュールをincludeすることによって、そのクラスから生成されるオブジェクトにおいて、モジュールに定義されたメソッドが使えるようになる。
module Secret
def secret
"My secret code is #{self.to_s.reverse}."
end
end
class Person
include Secret
end
my = Person.new
my.secret # => "My secret code is >46a32x0:nosreP<#."
モジュールもクラス同様オブジェクトだから、モジュールに対してもsingletonクラスを定義できる。
module Secret
end
class << Secret
def secret
"My secret code is #{self.to_s.reverse}."
end
end
Secret.secret # => "My secret code is terceS."
クラス同様略記法の方が一般的だ。
module Secret
def self.secret
"My secret code is #{self.to_s.reverse}."
end
end
Secret.secret # => "My secret code is terceS."
モジュールはModuleクラスから生成されるので、Moduleクラスに定義されたメソッドはすべてのモジュールで使えるようになる。
class Module
def secret
"My secret code is #{self.to_s.reverse}."
end
end
module Secret
end
Secret.secret # => "My secret code is terceS."
なおClassクラスはModuleクラスのサブクラスだから、Moduleクラスに定義されたメソッドはClassクラスに定義されたメソッドとなり、従ってすべてのクラスでも使えるようになる。
class Module
def secret
"My secret code is #{self.to_s.reverse}."
end
end
class Person
end
Person.secret # => "My secret code is nosreP."
動的メソッド定義 -define_method
メソッドはクラスにdef文で定義すると書いた。つまり通常メソッドはクラスの設計時に定義される。だけどメソッドを必要に応じて後から定義したい場合もある。
そのような場合メソッド定義をネストすればいい。
class Person
def method_maker
def name(word)
"My name is #{word}"
end
end
end
my = Person.new
my.method_maker
my.name "Charlie" # => "My name is Charlie"
こうすれば外側のメソッドが呼ばれたとき、初めて内側のメソッドが定義されるようになる。
でもせっかく後から定義するんだからもっと柔軟に定義できたらうれしい。例えばメソッド名をユーザの入力で決めるとか。
それに答えるのがModuleクラスのdefine_methodだ。
class Person
def method_maker(meth_name)
define_method(meth_name) do |word|
"My #{meth_name} is #{word}."
end
end
end
my = Person.new
my.method_maker(:name)
my.name "Charlie"
これでmethod_makerによってnameメソッドが作られて、myオブジェクトから呼べるようになる。
# ~> -:5:in `method_maker': undefined method `define_method' for #<Person:0x23f14> (NoMethodError)
# ~> from -:12
残念ながらこれはうまくいかないようだ。エラーメッセージをよく見ると、Personクラスのオブジェクトに対してdefine_methodを呼んでるようだ。つまりdef文の中のコンテキストは、Personクラスのオブジェクトということらしい。確かめてみよう。
class Person
def method_maker(meth_name)
p self
end
end
my = Person.new
my.method_maker(:name)
# >> #<Person:0x23c08>
やっぱりそうだ。def文の中ではselfはPersonクラスのオブジェクトになってる。Personクラスにメソッドを定義したいんだからそれじゃだめだ。Personクラスのコンテキストでdefine_methodを呼びたいんだ。
こんなときはeval系メソッドが使える。class_evalはクラスのコンテキストで渡されたブロックを評価する。だからPersonクラスに対してclass_evalを呼び出して、そのブロックの中でdefine_methodを定義すればいい。
class Person
def method_maker(meth_name)
Person.class_eval do
define_method(meth_name) do |word|
"My #{meth_name} is #{word}."
end
end
end
end
my = Person.new
my.method_maker(:name)
my.name "Charlie" # => "My name is Charlie."
my.method_maker(:old_name)
my.old_name "Henry" # => "My old_name is Henry."
my.method_maker(:size)
my.size "XXL" # => "My size is XXL."
クラスのユーザが後からメソッドを追加できるなんて素敵だ。
method_makerに可変長引数を渡せるようにすれば、もう少しよくなる。
class Person
def method_maker(*meth_names)
meth_names.each do |meth_name|
Person.class_eval do
define_method(meth_name) do |word|
"My #{meth_name} is #{word}."
end
end
end
end
end
my = Person.new
my.method_maker :name, :old_name, :size
my.name "Charlie" # => "My name is Charlie."
my.old_name "Henry" # => "My old_name is Henry."
my.size "XXL" # => "My size is XXL."
ここでdefine_methodは、Personクラスのコンテキストで評価されているので、Personクラスの特定のオブジェクト(my)でmethod_makerを呼ぶと、Personクラスの他のオブジェクトでは既にname, old_name, sizeの各メソッドが使える。
his = Person.new
his.name "Peter" # => "My name is Peter."
his.old_name "arthur" # => "My old_name is arthur."
his.size "M" # => "My size is M."
でも各オブジェクト毎に使えるメソッドを変えたい場合もある。そんなときはオブジェクトのsingletonクラスのコンテキストで、define_methodが評価されるようにすればいい。
class Person
def method_maker(*meth_names)
obj_singleton = class << self; self end
meth_names.each do |meth_name|
obj_singleton.class_eval do
define_method(meth_name) do |word|
"My #{meth_name} is #{word}."
end
end
end
end
end
my = Person.new
my.method_maker :name, :old_name, :size
my.name "Charlie" # => "My name is Charlie."
my.old_name "Henry" # => "My old_name is Henry."
my.size "XXL" # => "My size is XXL."
his = Person.new
his.name "Peter" # ~> -:25: undefined method `name' for #<Person:0x1f824> (NoMethodError)
his.method_maker :name, :age, :address
his.name "Peter" # => "My name is Peter."
his.age 21 # => "My age is 21."
his.address "New York" # => "My address is New York."
動的メソッド定義 -method_missing
ここまで来ると欲が出てくる。メソッドを追加するのにいちいちmethod_makerメソッドを、呼ばなければならないのはスマートじゃない。
そこでmethod_missingメソッドの出番だ。method_missingは呼び出されたメソッドが未定義のときに、Rubyが自動で起動するフックメソッドだ。
class Person < BasicObject
def initialize(*meths)
meths.each { |meth| __send__(meth) }
end
def method_missing(meth_name)
obj_singleton = class << self; self end
obj_singleton.class_eval do
define_method(meth_name) do |word|
"My #{meth_name} is #{word}."
end
end
end
end
my = Person.new :name, :old_name, :size
my.name "Charlie" # => "My name is Charlie."
my.old_name "Henry" # => "My old_name is Henry."
my.size "XXL" # => "My size is XXL."
his = Person.new :name, :age, :address, :class
his.name "Peter" # => "My name is Peter."
his.age 21 # => "My age is 21."
his.address "New York" # => "My address is New York."
his.class "Premier" # => "My class is Premier."
上のコードではinitializeメソッドにおいて、引き渡されたメソッド名を呼び出すようにしている。こうすればオブジェクトの生成時にmethod_missingが呼ばれて、対応するメソッドが定義されるようになる。
でもユーザがクラス階層にある既存のメソッドを引き渡した場合、Rubyはそのクラス階層をすべて探索して、そのメソッドを見つけるからmethod_missingは当然呼ばれない。例のようにPersonクラスをBasicObjectのサブクラスにすれば、そのリスクは最小になる(例ではObject#class)。
さらにmethod_missingが、定義したメソッドの結果を返すようにすれば、オブジェクト生成時にメソッド名を渡す必要もなくなる。
class Person < BasicObject
def method_missing(meth_name, word)
obj_singleton = class << self; self end
obj_singleton.class_eval do
define_method(meth_name) do |word|
"My #{meth_name} is #{word}."
end
end
__send__("#{meth_name}", word) #定義したメソッドを返す
end
end
my = Person.new
my.name "Charlie" # => "My name is Charlie."
my.old_name "Henry" # => "My old_name is Henry."
my.size "XXL" # => "My size is XXL."
his = Person.new
his.name "Peter" # => "My name is Peter."
his.age 21 # => "My age is 21."
his.address "New York" # => "My address is New York."
his.class "Premier" # => "My class is Premier."
上の例では未定義のinstanceメソッドの呼び出しに対して、method_missingが起動して対応するinstanceメソッドが定義されるようにした。
こうなると未定義のクラスメソッドの呼び出しに対しても、method_missingを起動させて対応するクラスメソッドを定義できるようにもしたくなる。その場合、method_missingおよびdefine_methodをクラスのsingletonクラスのコンテキスト、つまりクラスメソッドで定義すればいい。
class Country < BasicObject
def self.method_missing(meth_name)
cls_singleton = class << self; self end
cls_singleton.class_eval do
define_method(meth_name) do |word|
instance_variable_set("@#{meth_name}", word)
end
end
end
capital; language; population
end
class Japan < Country
capital "Tokyo"
language "Japanese"
population 127433494
def self.to_s
"Japan\n Capital: #{@capital}\n Language: #{@language}\n Population: #{@population}"
end
end
class Denmark < Country
capital "Copenhagen"
language "Danish"
population 5475791
def self.to_s
"Denmark\n Capital: #{@capital}\n Language: #{@language}\n Population: #{@population}"
end
end
puts Japan
puts Denmark
# =>
Japan
Capital: Tokyo
Language: Japanese
Population: 127433494
Denmark
Capital: Copenhagen
Language: Danish
Population: 5475791
この例ではCountryクラスの最後でクラスメソッドを呼ぶことでそれらを定義し、CountryクラスのサブクラスであるJapanとDenmarkで、それらのメソッドを使っている。定義したメソッドはメソッド名のインスタンス変数に引数をセットする。
なおBasicObjectは他のクラス同様、Classクラスのオブジェクトなので、instanceメソッドの場合と異なって、そこから継承された多数のクラスメソッドがある。だからそれらとの衝突が起きないよう注意が必要だ。
まとめ
- メソッドはクラスに定義される。
- クラスに定義されるメソッドはそのクラスから生成される各オブジェクトで使える。
- クラスはClassクラスのオブジェクト(インスタンス)だからClassクラスに定義されたメソッドは各クラスで使える。
- singletonクラスに定義されるメソッドはそのオブジェクト専用になる。
- クラスメソッドとはクラスのsingletonクラスに定義されたメソッドである。
- モジュールに定義されるメソッドはそれがincludeされたクラスのオブジェクト(インスタンス)で使える。
- メソッド定義をネストすれば外側のメソッドが呼ばれたときに内側のメソッドが定義されるようになる。
- class_evalとdefine_methodを使って動的にメソッドを定義できる。
- method_missingを使って自動的にメソッドが生成されるようにできる。
追記2008-10-26:Countryクラスでclass_variable_setでなくinstance_variable_setを使うよう修正。
- 適切じゃないけど理解を容易にするために ↩
blog comments powered by Disqus