10. モジュール

モジュールとは

Rubyにおけるモジュールとは、「まとまりのあるメソッドを持った部品」のことです。
モジュールを用いると、関連性を持ったメソッドのかたまり(部品)に名前をつけて管理することできます。

まずは、簡単な例を見てみましょう。
以下はどんなプログラムでしょうか。

methods.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 加算
def add(a, b)
  a + b
end

# 減算
def subtract(a, b)
  a - b
end

# 乗算
def multiply(a, b)
  a * b
end

# 徐算
def divide(a, b)
  a / b
end

num_a = 6
num_b = 3

puts "#{num_a} + #{num_b} = #{add(num_a, num_b)}" #=> 9

puts "#{num_a} - #{num_b} = #{subtract(num_a, num_b)}" #=> 3

puts "#{num_a} * #{num_b} = #{multiply(num_a, num_b)}" #=> 18

puts "#{num_a} / #{num_b} = #{divide(num_a, num_b)}" #=> 2

def add / subtract / multiply / divide ~ end は、前章で学んだメソッドの定義ですね。

それでは、これらのメソッドをモジュールを使ってまとめるとどうなるでしょうか。
試しに以下を実行してみましょう。
※ 実際の書き方についてはこの後で説明しますので、まずは雰囲気をつかんでもらえればOKです。

module.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 計算機能を提供するモジュール
module Calculator
  module_function

  # 加算
  def add(a, b)
    a + b
  end

  # 減算
  def subtract(a, b)
    a - b
  end

  # 乗算
  def multiply(a, b)
    a * b
  end

  # 徐算
  def divide(a, b)
    a / b
  end
end

num_a = 6
num_b = 3

puts "#{num_a} + #{num_b} = #{Calculator.add(num_a, num_b)}" #=> 9

puts "#{num_a} - #{num_b} = #{Calculator.subtract(num_a, num_b)}" #=> 3

puts "#{num_a} * #{num_b} = #{Calculator.multiply(num_a, num_b)}" #=> 18

puts "#{num_a} / #{num_b} = #{Calculator.divide(num_a, num_b)}" #=> 2

methods.rbで定義した4つのメソッドを一つにまとめて、「計算機」を意味する Calculator というモジュール名をつけてまとめています。
また、これらのメソッドが Calculator.{メソッド名} の形で呼び出されていることがわかります。
4つのメソッドがどんな用途で使われるのかがわかりやすいですね。

このように、モジュールを使うことで今までバラバラに定義していたメソッド群が意味を持った集合になり、プログラムの可読性が向上します。
それでは、実際にモジュールの作り方について学んでいきましょう。

モジュールの定義

モジュールは、次の形で定義されます。

1
2
3
4
5
6
7
8
9
10
11
# モジュール名はキャメルケース
module ModuleName
  module_function

  # メソッドの定義
  def method_name

    # メソッドの処理を書く

  end
end

モジュールは、module モジュール名 ~ end の形で定義し、その中にメソッド定義を書いていきます。
Rubyでは、モジュール名は先頭大文字のキャメルケースとします。

モジュール関数の実行

モジュール内に定義されたメソッド(モジュール関数)は、以下のような形で実行することができます。

1
  ModuleName.method_name

ここで注目してほしいのは、10-2.rbの3行目です。
モジュールの中で定義されたメソッドは、事前にmodule_functionという記述をして初めて外部から呼び出すことが可能になります。
試しに10-2.rbの3行目にあるmodule_functionをコメントアウトして実行してみましょう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 計算機能を提供するモジュール
module Calculator
  # module_function ← コメントアウト

  # 加算
  def add(a, b)
    a + b
  end

  # (省略)

end

num_a = 6
num_b = 3

puts "#{num_a} + #{num_b} = #{Calculator.add(num_a, num_b)}"
#=> undefined method `add' for Calculator:Module (NoMethodError)

では、module_function をモジュール定義の一番最後に入れた場合はどうでしょうか?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 計算機能を提供するモジュール
module Calculator
  # 加算
  def add(a, b)
    a + b
  end

  # (省略)

  module_function # ← モジュール定義の最後に移動
end

num_a = 6
num_b = 3

puts "#{num_a} + #{num_b} = #{Calculator.add(num_a, num_b)}"
#=> undefined method `add' for Calculator:Module (NoMethodError)

同じようにエラーになりましたね。
この場合は、module_functionに定義したメソッド名を渡してあげることで実行することができます。

module_function.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 計算機能を提供するモジュール
module Calculator
  # 加算
  def add(a, b)
    a + b
  end

  # 減算
  def subtract(a, b)
    a - b
  end

  # 乗算
  def multiply(a, b)
    a * b
  end

  # 徐算
  def divide(a, b)
    a / b
  end

  module_function :add, :subtract, :multiply, :divide
end

num_a = 6
num_b = 3

puts "#{num_a} + #{num_b} = #{Calculator.add(num_a, num_b)}" #=> 9

puts "#{num_a} - #{num_b} = #{Calculator.subtract(num_a, num_b)}" #=> 3

puts "#{num_a} * #{num_b} = #{Calculator.multiply(num_a, num_b)}" #=> 18

puts "#{num_a} / #{num_b} = #{Calculator.divide(num_a, num_b)}" #=> 2

時間の都合上、ここではなぜmodule_functionというものが必要なのかについては割愛しますが、これがないと外部からメソッドを実行できないことを覚えてください。

モジュールの定数

ここまではモジュールはメソッドをまとめるものとして説明しましたが、モジュールには定数も定義することができます。
定義した定数はモジュール名::定数名で呼び出すことができます。
以下はCalculator モジュールに定数として円周率PIを定義した例です。

module_constant.rb
1
2
3
4
5
6
7
8
9
10
# 計算機能を提供するモジュール
module Calculator
  PI = 3.14

  module_function

  # (省略)
end

puts Calculator::PI #=> 3.14

モジュールのネスト

モジュールは、入れ子にすることができます。
例として、10-2.rbで作成したCalculatorを子モジュールとして、レジ機能を提供する親モジュールCashRegisterで包んでみます。

cash_register.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# レジ機能を提供するモジュール
module CashRegister

  # 計算機能を提供するモジュール
  module Calculator
    module_function

    # 加算
    def add(a, b)
      a + b
    end

    # 減算
    def subtract(a, b)
      a - b
    end

    # 乗算
    def multiply(a, b)
      a * b
    end

    # 徐算
    def divide(a, b)
      a / b
    end
  end
end

num_a = 6
num_b = 3

puts CashRegister::Calculator.add(num_a, num_b) #=> 9

子モジュールのメソッドを呼び出す場合は、 親モジュール::子モジュール.メソッド名 となります。

もちろん、親モジュールに対してメソッドを定義することも可能です。
以下は、価格を渡すと税込価格を出力するメソッドを CashRegister に追加した例です。

calc_tax_in_price.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# レジ機能を提供するモジュール
module CashRegister
  module_function

  def calc_tax_in_price(price)
    Calculator.multiply(price, 1.08).round
  end

  # 計算機能を提供するモジュール
  module Calculator
    module_function

    # (省略)

    # 乗算
    def multiply(a, b)
      a * b
    end

    # (省略)
  end
end

# 税抜き価格
price = 100

puts CashRegister.calc_tax_in_price(price) #=> 108

モジュールの切り出し

モジュールの使い方がわかったところで、モジュールを別ファイルに切り出して読み込むようにしてみましょう。
練習用ディレクトリに module_study というディクレトリを作成し、中に calculator.rb というファイルを作成してください。

1
2
3
examples/
  └ module_study/
    └ calculator.rb
calculator.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 計算機能を提供するモジュール
module Calculator
  module_function

  # 加算
  def add(a, b)
    a + b
  end

  # 減算
  def subtract(a, b)
    a - b
  end

  # 乗算
  def multiply(a, b)
    a * b
  end

  # 徐算
  def divide(a, b)
    a / b
  end
end

次に、 module_study ディレクトリの中に、このファイルを呼び出す main.rb というファイルを作成してください。

1
2
3
4
examples/
  └ module_study/
    ├ main.rb       : ファイルを追加
    └ calculator.rb
main.rb
1
2
3
4
5
6
7
8
9
10
11
12
require_relative './calculator.rb' # Calculatorモジュールの読み込み

num_a = 6
num_b = 3

puts "#{num_a} + #{num_b} = #{Calculator.add(num_a, num_b)}" #=> 9

puts "#{num_a} - #{num_b} = #{Calculator.subtract(num_a, num_b)}" #=> 3

puts "#{num_a} * #{num_b} = #{Calculator.multiply(num_a, num_b)}" #=> 18

puts "#{num_a} / #{num_b} = #{Calculator.divide(num_a, num_b)}" #=> 2

試しに、 main.rb を実行してみましょう。
モジュールの機能を呼び出すことができたでしょうか。

ここで、require_relativeというのは、外部(今参照しているファイルの外)からrubyのファイルを読み込むためのメソッドです。
relative(相対的な)という単語からもわかるように、現在参照しているファイルからの相対パスで読み込み先のファイルを指定します。
ちなみに、require というメソッドも存在しますが、こちらは12章で説明します。

それでは、先ほど module_study/ 直下においたモジュールを、 lib/ というディレクトリを作成して置いてみましょう。

1
2
3
4
5
examples/
  └ module_study/
    ├ main.rb
    └ lib/     : ディレクトリを追加
      └ calculator.rb

モジュールのパスが変わるので、 main.rb の1行目を以下のように変えましょう。

1
require_relative './lib/calculator.rb'

先ほどと同じように呼び出せたでしょうか?
このように、モジュールを一つのディレクトリにまとめておけば、コードを見るときにもわかりやすいですね。

1
2
3
4
5
6
7
examples/
  └ module_study/
    └ lib/
      ├ foo.rb
      ├ bar.rb
      ├ baz.rb
      :

モジュールの利点

ここまで説明した内容だけでは、モジュールの利点を理解するのは難しいかもしれません。
そこで、モジュールを利用することで得られる恩恵を以下にまとめておきます。

可読性の向上

モジュールとは」で説明したように、メソッドを機能のまとまりごとに管理することで、プログラムの書き手も読み手もわかりやすくなります。

名前空間の提供

次を実行するとどうなるでしょうか?

double_hello.rb
1
2
3
4
5
6
7
8
9
def hello
  puts 'こんにちは、Aだよ!'
end

def hello
  puts 'こんにちは、Bだよ!'
end

hello #=> こんにちは、Bだよ!

このように、既存のメソッド名に対して、同じ名称を新しいメソッドとして定義すると内容が上書きされてしまいます。
これを名前の衝突といいます。
例えば、Aさんが作ったメソッドhelloとBさんが作ったメソッドhelloをそれぞれ別の名前のモジュールに入れておくことで、衝突を避けることができます。

name_space.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module ModuleA
  module_function
  def hello
    puts 'こんにちは、Aだよ!'
  end
end

module ModuleB
  module_function
  def hello
    puts 'こんにちは、Bだよ!'
  end
end

ModuleA.hello # => こんにちは、Aだよ!

多重継承の実現

こちらはクラスの「継承」という概念が必要になるのでここでは割愛しますが、簡単にいうと以下の性質があります。

  • 「クラス」は一つのクラスしか引き継げない
  • 「モジュール」は一つのクラスの中で複数引き継げる

つまり、一つのクラスにたくさんの機能のまとまりを持たせたい場合、モジュールを利用します。