Module
Rubyにはクラス以外にもメソッドや定数を提供する手段として、モジュールという仕組みが存在します。 モジュールでは以下のようなことを実現することができます。
- クラスと同じように定数やメソッドをまとめる
- クラスに組み込んで多重継承を実現する
- クラス名、定数名の衝突を防ぐための名前空間を提供する
ClassとModuleの違い
クラスはオブジェクトを生成する元となるプログラムです。クラスの特徴としては以下のようなものが挙げられます。
- インスタンスを生成できる
- 継承を用いて親クラスからメソッドや定数を呼び出すことができる
これに対してモジュールはインスタンスを生成することはできません。継承することも出来ません。 しかしモジュールにはクラスにおける継承と似た仕組みとして、ミックスインというものが存在します。 モジュールをクラスに組み込み(ミックスイン)することで、モジュールに定義してある定数やメソッドを組み込み先のクラスから呼び出すことが出来るようになります。
ModuleをClassにinclude/extendする
Rubyのクラスでは単一継承が採用されており、一つのクラスは一つの親クラスしか持てません。 また、継承の原則はis-aの関係であること。つまり”AはBである”が成り立たないクラス同士では継承の使用は避けるべきです。 しかし現実の開発において、複数クラスにまたがって共通の機能が必要となるシーンは多々存在します。
ここで活躍するのがモジュールです。 前述の通り、モジュールはクラスに組み込むことでモジュールに定義したメソッドを呼び出すことができます。 モジュールを使うことで、本来単一継承しか行えないクラスに対して多重継承を実現することが可能となります。 モジュールの組み込み方法にはincludeとextendの2つのパターンが存在します。
include
includeでは、対象のクラスに組み込んだモジュールのメソッドがインスタンスメソッドとして組み込まれます。 Class.newで作成したインスタンスに対して呼び出すことが可能です。 以下は引数として与えた文字列をログとして出力する汎用的なメソッドを持たせたLoggableモジュールを、 商品クラスにincludeして使用する一例です。
module Loggable def log(text) puts"[LOG] #{text}" end end class Product include Loggable end product = Product.new product.log('hello world') #=> "[LOG] hello world" # 伊藤 淳一. プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法まで (Software Design plus) (Japanese Edition) (Kindle の位置No.6899-6900). Kindle 版. より一部抜粋
extend
extendでは、対象のクラスに組み込んだモジュールのメソッドがクラスメソッドとして組み込まれます。 インスタンスからの呼び出すは不可で、クラスに対して直接呼び出すことが可能です。 以下は上述の例と同様のモジュールをextendでミックスインした場合の例となります。 メソッドのレシーバーがインスタンスではなくクラスになっている点に注目です。
module Loggable def log(text) puts"[LOG] #{text}" end end class Product extend Loggable end Product.log('hello world') #=> "[LOG] hello world" # 伊藤 淳一. プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法まで (Software Design plus) (Japanese Edition) (Kindle の位置No.6931). Kindle 版. より一部抜粋
名前空間を提供して命名の衝突を避ける
例えば一つのアプリケーション内で、ユーザーの属性が管理ユーザーと一般ユーザーに分かれているものは多々あるかと思います。 この権限が異なるユーザーをそれぞれ別々のクラスで管理しており、それらを何らかの理由で同時に使用しなければならない場合に、 モジュールを名前空間として用いることで名前の衝突を避けることが可能です。
# 管理用ユーザー class User extend Loggable end # 一般ユーザー class User extend Loggable end # どちらに対する呼び出しか判別できない User.log('admin') User.log('standard')
module Admin class User extend Loggable end end # 一般ユーザー class User extend Loggable end # 名前空間としてモジュールで囲うことにより衝突を回避 Admin::User.log('admin') #=> "[LOG] admin" User.log('standard') #=> "[LOG] standard"
まとめ
まとめるとモジュールの用途としては主に以下のようなシーンが想定されます。
- 継承は使用できないが複数のクラスで横断的に使用したい共通のメソッドや定数を定義したい場合
- 多くのクラスから利用される汎用的な共通メソッドを定義したい場合
- 同名で属性の異なるクラスに名前空間を提供して名前の衝突を防ぎたい場合