<http://midorex.blogspot.com/2010/08/singletonclass.html> 前回からの続き
class Test
def initialize(h)
h.each{|k,v|
self.class.send(:attr_accessor, k)
self.send("#{k}=", v)
}
end
end
h={:cat=>'cat'}
t = Test.new(h)
t.cat = 'ddd'
p t
これを実行すると
#<Test:0x00000 @cat="ddd">
となる。でもでも :cat を attr_reader にしたかった場合はこのままだとうまくいかない。んで考えてみたこと。
まずは通常の書き方
class Test
attr_reader :cat, :dog
def initialize
@cat = 'meow'
@dog = 'bowwow'
end
end
t = Test.new
p t.instance_variables
p t.class.instance_methods[0..2]
p t.cat
t.cat = "cat"
実行結果
[:@cat, :@dog]
[:cat, :dog, :nil?]
"meow"
./draft.rb:15:in `<main>': undefined method `cat=' for #<Test:0x00000 @cat="meow", @dog="bowwow"> (NoMethodError)
最後の行でcat に代入しようとしても attr_reader だから代入できないことを確認。
この結果と同じ結果を導きだすには?
class Test1
def initialize(h)
h.each{|k,v|
self.class.send(:attr_reader, "#{k}")
self.send("#{k}=", v)
}
end
end
h = {:cat=>'meow', :dog=>'bowwow'}
t = Test1.new(h)
p t.instance_variables
p t.class.instance_methods[0..2]
p t.cat
t.cat = "cat"
実行結果
... `block in initialize': undefined method `cat=' for #<Test1:0x00000> (NoMethodError)
self.send("#{k}=", v) は attr_accessor の時は使えるけど attr_reader の時は使えない。
文字通り reader だから getter であって setter じゃないため instance 変数に"cat"を代入できないためにエラーがかえってきた。
ならば
self.send のかわりに普通に instance_variable_set を使ってみた。
class Test1
def initialize(h)
h.each{|k,v|
instance_variable_set("@#{k}", v)
self.class.send(:attr_reader, "#{k}")
}
end
end
h = {:cat=>'meow', :dog=>'bowwow'}
t = Test1.new(h)
p t.instance_variables
p t.class.instance_methods[0..2]
p t.cat
t.cat = "cat"
実行結果
[:@cat, :@dog]
[:cat, :dog, :nil?]
"meow"
./draft.rb:25:in `<main>': undefined method `cat=' for #<Test:0x00000 @cat="meow", @dog="bowwow"> (NoMethodError)
うまくいった。
instance_variable_set でインスタンス変数に値を代入しておいてその後に、getter を設定した。
self.class.send(:attr_reader, "#{k}")
を削除すると、[通常の書き方]から attr_reader を削除した場合と等しくなる。
# --------------------
次はインスタンス変数を定義しないし attr も必要ない場合。
定義したいのはinstance_method def cat と def dog だけ。
通常の書き方。
class Test
def initialize
end
def cat
"meow"
end
def dog
"bowwow"
end
end
t = Test.new
p t.instance_variables
p t.class.instance_methods[0..2]
p t.cat
実行結果
[]
[:cat, :dog, :nil?]
"meow"
上記と同じ結果を導くには?
class Test1
def initialize(h)
h.each{|k,v|
self.class.send(:define_method, k){v}
}
end
end
h = {:cat=>'meow', :dog=>'bowwow'}
t = Test1.new(h)
p t.instance_variables
p t.class.instance_methods[0..2]
p t.cat
実行結果
[]
[:cat, :dog, :nil?]
"meow"
まとめ
1, getter setter をinitialize 内で使う
self.class.send(:attr_accessor, k)
self.class.send(:attr_reader, k)
self.class.send(:attr_writer, k)
2, @cat のようなインスタンス変数に値を代入する (k はシンボル)
instance_variable_set("@#{k}", v)
# setter が定義されていることが必要条件
self.send("#{k}=", v)
3, def cat のようなインスタンスメソッドを定義する
self.class.send(:define_method, k){v}
+++ 追記 +++
attr_accessor は通常は クラス内の def initialize の上とか下に書く。
initialize の中には書かない。つか普通にエラーになる。
ということは、class Test のinstance が保有するメソッドじゃなくて
class Test が知っているメソッドだとわかるわけだけども
p Test.methods
とかやったって表示されない。じゃ一体どこにあるんだ?ということで下記を作ってみた。
4 module My
5 def self.search_method(x, key)
6 ary = x.methods
7 print "=== OBJECT:#{x} ===\n"
8 ary.each{|m|
9 next unless /methods/.match(m.to_s)
10 next if m == :methods
11 eval "a = x.send(m)
12 if a.index(key)
13 print \"[:#{key}] exist in #{m}\n\"
14 else
15 print \"# #{m}\n\"
16 end"
17 }
18 end
19 end
20
21 class Test
22 def initialize
23 My::search_method(self.class, :attr)
24 # My::search_method(self, :nil?)
25 end
26 end
27
28 Test.new
# 実行結果
=== OBJECT:Test ===
# instance_methods
# public_instance_methods
# protected_instance_methods
# private_instance_methods
# singleton_methods
# protected_methods
[:attr] exist in private_methods
# public_methods
クラスの「private_methods」に属していることがわかった。
結局...
self.class.send(:attr_accessor, k)
self.send("#{k}=", v)
こういう方法でインスタンス変数を定義するってことは、親の遺伝子を子供が外側から操作しているように思えてきてよくないような気もしてきた。
0 件のコメント:
コメントを投稿