Rubyのモジュールについて調べた
ちょっと前にモジュール関係で結構ハマって、なんでこれが動かないんだろうってのがあったので少し調べた。結局それはモジュール関係なくただの自分の(くそしょぼい)凡ミスのせいだったんだけど、せっかくちょっと調べたのでメモしておく。
はじめに
まずirbでこうやると、
$ irb irb(main):001:0> Class.superclass => Module irb(main):002:0> Module.superclass => Object
ModuleはClassのスーパークラスだとわかる。
つまり、ModuleはClassのもっと一般的なやつ。言い換えると、特殊なModuleがClass。
クラスとモジュールの違いは、リファレンスマニュアルを見ると、こう書かれてる。
クラスとモジュールには
- クラスはインスタンスを作成できるが、モジュールはできない。
- モジュールを他のモジュールやクラスにインクルードすることはできるが,クラスをインクルードすることはできない。
という違いがありますが、それ以外のほとんどの機能は Module から継 承されています。Module のメソッドのうち
- Module#module_function
- Module#extend_object
- Module#append_features
は Class では未定義にされています。
class Class - Ruby 2.2.0 リファレンスマニュアル
以下の特性を見ると、モジュールはあくまでクラスの機能を補助(拡張)するものというイメージ。
- モジュールはインスタンスを生成できない
- モジュールは他のクラスやモジュールにincludeして使われる
一番シンプルな例。
module M def foo 'module method' end end class C include M end puts C.new.foo #=> module method
Rubyでのモジュールの役割
以下二つの役割がある。
- Mix-in
- 名前空間
Mix-in
クラスやモジュールにモジュールをインクルードすることをMix-inと呼ぶ。
Rubyのクラスは多重継承できないが、Mix-inなら複数のモジュールをインクルードすることができる。
module M1 def foo 'method foo' end end module M2 def bar 'method bar' end end class C include M1 include M2 end puts C.new.foo puts C.new.bar #=> method foo #=> method bar
ずっとインクルードって書いてきたけど、モジュールを他のクラスやモジュールに取り込む機能としてはextendもある。
モジュールのincludeが、指定したモジュールのインスタンスメソッドをクラス(やモジュール)のインスタンスメソッドとして追加するのに対し、extendはクラス(やモジュール)の特異メソッドとして追加する。
つまり、クラスにモジュールをextendすると、モジュールに定義されたメソッドをクラスメソッドとして使えるようになる。
module M1 def foo 'method foo' end end # モジュールにextend module M2 extend M1 end # クラスにextend class C extend M1 end puts M2.foo puts C.foo #=> method foo #=> method foo
名前空間
クラス定義をモジュールで囲むことで、名前空間として利用できる。異なる名前空間であれば、同名のクラスも定義できる。
module M1 class Foo def foo 'foo' end end end module M2 class Foo def bar 'bar' end end end puts M1::Foo.new.foo puts M2::Foo.new.bar #=> foo #=> bar
その他もろもろ
以上が基本。当然もっと高度なトピックもある。
以下、調べてるときに出くわしたものの一部。さわりだけ。
クラスとの使いわけ
クラスAとクラスBに共通する機能を、クラスCにまとめて両クラスのスーパークラスとするのか、モジュールMにまとめて両クラスでインクルードするのかっていう判断は割と難しい(場合がある)と思う。たぶん、is-a関係とかhas-a関係とかを考慮に入れて判断するんだろう。
優先順位
インクルードしたモジュールにスーパークラスと同名のメソッドが定義されていた場合、モジュールが優先される。インクルードしたモジュールはサブクラスとスーパークラスの間に挟まれる継承関係になる。
module M1 def foo 'module foo' end end class A def foo 'class foo' end end class B < A include M1 end puts B.new.foo #=> module foo puts B.ancestors.join(' -> ') #=> B -> M1 -> A -> Object -> Kernel -> BasicObject
複数のモジュールをインクルードした場合は、後でインクルードしたモジュールのメソッドが優先。
module M1 def foo 'M1 foo' end end module M2 def foo 'M2 foo' end end class A include M1 include M2 end puts A.new.foo #=> M2 foo
instance method Module#include - Ruby 2.2.0 リファレンスマニュアル
module_function
メソッドをモジュール関数にします。
モジュール関数とは、プライベートメソッドであると同時に モジュールの特異メソッドでもあるようなメソッドです。 例えば Math モジュールのメソッドはすべてモジュール関数です。
class Module - Ruby 2.2.0 リファレンスマニュアル
module M1 def foo 'foo' end module_function :foo end class Foo include M1 end puts M1.foo #=> foo # fooはプライベートになる # puts Foo.new.foo #=> private method `foo' called for #<Foo:0x007f844b087998> (NoMethodError)
まとめ
当面はこれだけの知識があれば大丈夫そう。わからないことがあったら都度調べればいいや。