【Ruby】パーフェクトRuby 学習感想文 ~余談:Moduleのinclude、extend
何でModuleモジュールのプライベートメソッドが任意のクラスで見えるんだー、
って書いてたら
師匠からメールが来た
module をmix-inするには include extend の2種類あります。 この違いを理解すれば、わかるかと。
そうなのか。
なら理解してみよう!
状況のまとめ
p Module.private_instance_methods
■実行結果
[:included, :extended, :prepended, :method_added, :method_removed, :method_undefined, :initialize_copy, :attr, :attr_reader, :attr_writer, :attr_accessor, :initialize, :remove_const, :append_features, :extend_object, :include, :prepend_features, :prepend, :refine, :remove_method, :undef_method, :alias_method, :public, :protected, :private, :module_function, :define_method, :initialize_dup, :initialize_clone, :sprintf, :format, :Integer, :Float, :String, :Array, :Hash, :warn, :raise, :fail, :global_variables, :__method__, :__callee__, :__dir__, :eval, :local_variables, :iterator?, :block_given?, :catch, :throw, :loop, :respond_to_missing?, :trace_var, :untrace_var, :at_exit, :syscall, :open, :printf, :print, :putc, :puts, :gets, :readline, :select, :readlines, :`, :p, :test, :srand, :rand, :trap, :exec, :fork, :exit!, :system, :spawn, :sleep, :exit, :abort, :load, :require, :require_relative, :proc, :lambda, :binding, :caller, :caller_locations, :Rational, :Complex, :set_trace_func, :gem, :gem_original_require, :singleton_method_added, :singleton_method_removed, :singleton_method_undefined, :method_missing]
⇒ Moduleのプライベートメソッド一覧にattr_accessor がいる。
class Hoge end p Hoge.method(:attr_accessor) p Hoge.new.method(:attr_accessor)
■実行結果
#<Method: Class(Module)#attr_accessor> `method': undefined method `attr_accessor' for class `Hoge' (NameError)
⇒ Hogeオブジェクトと、Hoge.newオブジェクトで見えてるメソッドが違う
なんでHoge.methodだと、他のクラスのプライベートメソッドまで取れるんだろう。
いまこういう状況です。
include? exclude?
本にはこう書いてある(P131,133)
インスタンスメソッドとしてモジュールを取り込むにはincludeを用います。 オブジェクトにモジュールのメソッドを取り込むにはextendを用います。 クラスもまたオブジェクトの一つですので、モジュールをextendすることが出来ます。
include インスタンスメソッドとして取り込む
extend クラスメソッドとして取り込む
module Momo def hello p "oha" end end class Aaa include Momo end class Bbb extend Momo end Aaa.new.hello Bbb.hello
■実行結果
"oha" "oha"
ふむ。
プライベートだったら?
module Momo def hello p "oha" end private :hello end class Aaa include Momo end class Bbb extend Momo end Aaa.new.hello Bbb.hello
■実行結果
private method `hello' called for #<Aaa:0x2cf7c18> (NoMethodError) private method `hello' called for Bbb:Class (NoMethodError)
どっちもエラー。
そうやんね。プライベートメソッドはレシーバつきで呼び出せないから。
あ!
クラスの地の文のselfはClassクラスのオブジェクトだった!
⇒http://sekai.hateblo.jp/entry/2013/08/22/063502
だから、
extendしたクラスの地の文では、Momoモジュールのメソッドをレシーバなしで呼べる
のでは!?
やってみよう
module Momo def hello p "oha" end private :hello end class Aaa include Momo end class Bbb extend Momo hello end p Bbb.method(:hello)
■実行結果
"oha" #<Method: Class(Momo)#hello>
やっぱり!!
Modleをextendしたクラスは、クラス内の地の文では、Moduleのプライベートメソッドが実行できるんだ!
Rubyにおけるprivateの概念って、自分のクラスで閉じているという意味ではなくて、
レシーバがある・ない、で考えたほうがしっくり来る。
レシーバを省略した形でも呼べる場所であるかぎり、どこでも呼べる。
挫折の状況(ふりかえり)
前はこんなコードを書いていた↓。 Sekai.attr_sekaiじゃないと実行できなかった。
module Sekai def self.attr_sekai(attr) eval <<-EOD @#{attr} def #{attr}=(val) @#{attr} = val end def #{attr} @#{attr}.upcase end EOD end end class Hoge include Sekai Sekai.attr_sekai :name end hoge = Hoge.new hoge.name= "tanaka" p hoge.name
■実行結果
"TANAKA"
こうすれば↓
module Sekai def attr_sekai(attr) eval <<-EOD @#{attr} def #{attr}=(val) @#{attr} = val end def #{attr} @#{attr}.upcase end EOD end private :attr_sekai end class Hoge extend Sekai attr_sekai :name end hoge = Hoge.new hoge.name= "tanaka" p hoge.name
■実行結果
undefined method `name=' for #<Hoge:0x2b874d0> (NoMethodError)
レシーバなしで attr_sekai がよびだせた。
でもextend Sekai にしてしまうとeval で作成するメソッドも
クラスメソッドとして認識されちゃう。だからエラー。
こんなコードしか実行できない↓
module Sekai def attr_sekai(attr) eval <<-EOD @#{attr} def #{attr}=(val) @#{attr} = val end def #{attr} @#{attr}.upcase end EOD end #private :attr_sekai end class Hoge extend Sekai attr_sekai :name end Hoge.name ="maeda" p Hoge.name
■実行結果
"MAEDA"
モジュールにクラスメソッド/インスタンスメソッド、
どちらも持ちたい場合はどうすれば?
まて次号(あるのか?)