Ruby Module(モジュール)

  • モジュールを使うと、メソッドを共同利用できるようになる
    • 複数のクラスでも、モジュールによってメソッドを共同利用できる
  • クラスからモジュールをインクルードすることで
    • モジュールに定義したメソッドを、あたかもそのクラス自身に定義されたメソッドとして使えるようになる

  • ホイップクリームのトッピング
    • ドリンクでもケーキでも楽しめる
  1. ホイップクリームをトッピングするメソッドを作る
  2. メソッドをドリンク・ケーキのクラスで共同利用する

複数のクラスでメソッドを共同利用する手順

  1. モジュールを作る
  2. モジュールにメソッドを定義する
  3. モジュールのメソッドをクラスで使う

モジュールを作る

module モジュール名
end
module WhippedCream
end

モジュール名の命名 ModuleName : キャメルケース

  • クラス同様に、先頭は大文字・キャメルケースでモジュール名を定義する

クラスとの違い

  • 定義方法は似ているが、クラスとの違いは
    • インスタンス(オブジェクト)を作ることができない
    • 主にメソッドを共同利用するための部品

モジュールにメソッドを定義する

  • クラス同様に
    • インスタンスメソッド・クラスメソッド(正確にはモジュールメソッド)を定義できる
    • インスタンス変数や引数、戻り値なども同様に使える
module AddCream
  def add_cream
    @name += " with cream"
  end
end

モジュールのメソッドをクラスで使う

  • 定義したモジュールをクラスで利用するには、include メソッドでモジュールを指定してインクルードする
class クラス名
  include モジュール名
end
module AddCream
  def add_cream
    @name += " with cream"
  end
end

class Drink
  include AddCream # モジュールをインクルード
  def initialize(name)
    @name = name
  end
  def name
    @name
  end
end

drink = Drink.new("mocha")
drink.add_cream # モジュール定義のメソッドを呼び出し
p drink.name
# "mocha with cream"

モジュールを複数のクラスで共同利用する

module AddCream
  def add_cream
    @name += " with cream"
  end
end

class Item
  include AddCream
  def initialize(name)
    @name = name
  end
  def name
    @name
  end
end

class Drink < Item
end

class Food < Item
end

drink = Drink.new("mocha")
drink.add_cream
p drink.name
# "mocha with cream"

food = Food.new("cake")
food.add_cream
p food.name
# "cake with cream"

モジュールの使いどころ

  • クラスの継承は、「親クラスから派生する子クラス」といった関係性が曖昧になると、コード全体に違和感が生じる
  • モジュールの場合は上記のような関係性は気にする必要はないため、「クラスを継承することが適切でない」といった場面でも気にせず利用できる

Enumerable モジュール

  • 例)配列に対して「全要素が該当しない」ことを調べる none? メソッド
p [1, 2].none? {|i| i == 0} # true
p [1, 2].none? {|i| i == 1} # false
  • 配列に対して使える none? メソッドだが、リファレンスの Array クラスのページには記載がない
    • Enumarable モジュールのページに記載がある
  • Array クラスは、Enumarable モジュールをインクルードしているため、このメソッドを利用できる
    • Enumarable モジュールにはたくさんのメソッド群が定義されているので、他の多数のメソッドも利用できる
  • Hash クラスも、Enumarable モジュールをインクルードしているため、このメソッドを利用できる
h = {a: 1, b: 2}
p h.none? {|k, v| v == 0} # true
p h.none? {|k, v| v == 1} # false

extend モジュールのメソッドをクラスメソッドにする

  • モジュールのメソッドをクラスで include すると、インスタンスメソッドとして利用できる
  • モジュールのメソッドをクラスで extend すると、クラスメソッドとして利用できる
module Greeting
  def hello
    "Hello!"
  end
end

class Shop
  extend Greeting
end

p Shop.hello
# "Hello!"
  • extend したモジュールのメソッドを、インスタンスメソッドで呼び出そうとするとエラーになる
shop = Shop.new
p shop.hello
# undefined method `hello' for #<Shop:0x000000010cd8c1a8> (NoMethodError)

モジュールのメソッドや定数をそのまま使う

  • モジュールにクラスメソッドや定数を定義しておき、そのまま呼び出すこともできる

クラスメソッド呼び出し

module Greeting
  def self.hello
    "Hello!"
  end
end

p Greeting.hello # "Hello!"

定数呼び出し

モジュール名::定数名
module Greeting
  Default = "Hello, world."
end

p Greeting::Default # "Hello, world."

Ruby が用意しているモジュール

  • 自分で定義する以外に、予め Ruby で用意されたモジュールを使うこともできる
  • 例)Math モジュール
    • 定数 PI
p Math::PI
# 3.141592653589793

名前空間

  • 同じクラス名を複数の場所で使いたい
    • ただ、それぞれ別のクラスのため別々に定義して呼び分けたい
class Beer
  def self.info
    "KIRIN beer"
  end
end

class Beer
  def self.info
    "ASAHI beer"
  end
end

p Beer.info
# "ASAHI beer"
  • モジュールを使って名前を付け分けるという方法がある
    • これを「名前空間を作る」という
module Kirin
  class Beer
    def self.info
      "KIRIN beer"
    end
  end
end

module Asahi
  class Beer
    def self.info
      "ASAHI beer"
    end
  end
end

p Kirin::Beer.info
# "KIRIN beer"
p Asahi::Beer.info
# "ASAHI beer"
クラス名(またはモジュール名)::クラス名(またはモジュール名)

クラスとモジュールの使い分け

  • クラスとモジュールは、どちらでも同等のことができる
    • インスタンスを
      • 作るとき → クラスを使う
      • 作らないとき → モジュールを使う
    • と使い分けることで、その意図が伝わりやすい
  • 上記の例のように、単に名前空間を分けることが目的ならインスタンスを作る必要がないため、モジュールを使うとよい