ガラシのパルプンテ頼み

地方産限界エンジニアのグローバル独り言

【5分くらいでわかる】Moduleの使い方 | Ruby入門

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"

まとめ

まとめるとモジュールの用途としては主に以下のようなシーンが想定されます。

  • 継承は使用できないが複数のクラスで横断的に使用したい共通のメソッドや定数を定義したい場合
  • 多くのクラスから利用される汎用的な共通メソッドを定義したい場合
  • 同名で属性の異なるクラスに名前空間を提供して名前の衝突を防ぎたい場合