RubyのModuleについて調べてみた

/

Module

クラスはオブジェクト志向言語であるRubyにとって重要な概念です。
オブジェクト志向プログラミングではクラスを定義し、データとメソッドを一つのオブジェクトとしてまとめる事でプログラムのカプセル化を実現していますが、Rubyではクラスの他にモジュールと呼ばれる機能を使う事でも変数やメソッドをグループ化する事が出来ます。

class Module/Ruby 2.3.0 class Module

モジュールの定義

Rubyのモジュールはmoduleキーワードを使って定義します。定義するモジュール名の先頭の文字はクラス同様に大文字にする必要があります。

modlue Foo
end

モジュールの定義の方法はクラスの定義と似ていますが、モジュールはクラスと違いインスタンスを作る事はで出来ず、またサブクラス化する事も出来ません。

以下はそれぞれエラーが発生します。

module Foo
end

foo = Foo.new
module Foo
end

class Bar < Foo
end

名前空間

Rubyのモジュールは名付けられた名称によって、その内部にあるクラスや変数、メソッドのスコープの範囲を設定するため名前空間としての役割を果たします。

名前空間により各プログラムの影響するスコープの範囲を区切ることで、複数のライブラリ化したプログラムを利用している場合でもそれぞれのプログラム内で付けられた名称の衝突を回避し、参照を容易にする事が出来るようになります。

以下の例ではモジュールによって名前空間分かれているため、同名のメソッドが異なる振る舞い方をしています。

module USA
  def self.greeting
    puts "Hello"
  end
end


module Germany
  def self.greeting
    puts "Guten Tag"
  end
end

USA.greeting # Hello
Germany.greeting # Guten Tag

ネスト

名前空間としてのモジュールはクラスや別のモジュールをネストして使う事が出来ます。

module California
  class LosAngels
    @@population = 3880000
    def get_population
      return @@population
    end
  end
end

los_angels = California::LosAngels.new
p los_angels.get_population #3880000

module NewYork
  class Manhattan
    @@population = 1610000
    def get_population
      return @@population
    end
  end
end

manhattan = NewYork::Manhattan.new
p manhattan.get_population #1610000

ネストしたモジュールは呼び出す時と同様に::を使って、モジュールの外側から定義する事も可能です。

module Outer
  module Inner
  end
end

module Outer::Inner
  def self.greeting
    puts "Hello!!"
  end
end

Outer::Inner.greeting

名前空間を使う理由

Rubyのプログラム内ではモジュールによって区切られたプログラムは直接には共通の名前空間(モジュール)同士でしか影響を与えません。そのため異なる名前空間を利用すれば同じ名前のメソッドやクラス同士の衝突を回避する事が出来ます。

Rubyにおける名前空間の性質の利用方の一つがgem(パッケージ)の一番外側をパッケージと同じ名前のmoduleでくくってしまい、その中に必要な要素を含むというものです。

例えばbundlerならbundler、nokogiriならnokogiriという名前のモジュールを作ってパッケージ全体を他と被らない名前のグループでくくってしまえば、各パッケージが影響する範囲は名前空間によって区別されるため、あるプログラムで使用している別々のパッケージの内部で同じ名前のクラスや変数が使われていても互いが干渉しあうという自体を避ける事が出来るようになります。

この事を理解しておけば各パッケージが影響を与える範囲が限定出来るため、自分のプログラムが予想しない動きをするのを恐れて他人の作ったgem(パッケージ)の中身の全て調べるという作業をする必要はありません。
つまりパッケージを利用してプログラムを書くプログラマーはプログラムを書く際にパッケージの中身の実装を知らずとも、その振る舞いを理解していれば望んだ動作をするプログラムを書くことが出来るという事になります。

Mixin

Rubyにおけるモジュールの扱い方のもう一つはMixinです。

Rubyのクラスは別のモジュールで定義されたメソッドを取り込んで使用する事が可能で、この機能をMixinと呼びます。
Mixinはクラスからincludeを使って、モジュールのメソッドをクラスのインスタンスメソッドとして読み込む事で実行出来ます。

module Dram
  def hit()
    puts "ダン! ダン!"
  end
end

class SnareDram
  #モジュールのMixin
  include Dram
end

snare_dram = SnareDram.new
#Moduleから読みこんだインスタンスメソッドをMixinしたクラスのインスタンスで使う
snare_dram.hit
$ ruby app.rb
ダン! ダン!

Mixinはクラスの継承と似ていますが、Rubyのクラスでは同時に複数のクラスを継承する、いわゆる多重継承が出来ないのに対してMixinでは複数のモジュールのメソッドを同時にクラスに取り込む事が出来ます。

class RightFielder
  def hitting
    puts "カン!!"
  end
end

class Pitcher
  def pitching
    puts "ビュン!!"
  end
end

#こういった書き方は出来ない
class Ichiro < RightFielder, Pitcher
end

#Mixinでは同時に複数のモジュールのメソッドを取り込める
class Ichiro
  include RightFielder,Pitcher
end

include & extend

Mixinでクラスにメソッドを取り込むにはincludeの他にextendを使う事でも実行出来ます。

両者にはクラスで取り込んだモジュールのメソッドを

  • include: インスタンスメソッド
  • extend: 特異メソッド

として定義するという違いがあります。

module Japanese
  def greeting
    puts "おはよう!!"
  end
end

class Matsui
  extend Japanese
end

#特異メソッドとして実行
Matdui.greeting
ruby .app.rb
おはよう!!

extendによってクラスに取り込まれたメソッドは特異メソッドとして定義されるため、当然そのクラスから作られたインスタンスから実行する事は出来ません。

Mixinの定義

特異メソッドが少しややこしい事と最初にRubyのMixinはモジュールのメソッドをクラスの「インスタンスメッソド」として取り込む事だと憶えてしまったせいで、僕はMixinという言葉がincludeによってメソッドを取り込む事を指すのか、extendで特異メソッドを継承する事も含まれるのか曖昧なまま使っていたのですが、以下のような記述を見つけたので両方をMixinと呼んで問題ないようです。

A Mixin adds features from a module into another context. RDoc::Include and RDoc::Extend are both mixins.
class RDoc::Mixin – docs.ruby-lang.org

あとがき

モジュールとMixinという名前がキーワードとしてあまり見ない上に、モジュールが特異メソッドとも関わるために理解するのに苦労しましたがmoduleは名前空間として機能し、メソッドの継承が出来るという事を押さえておけば、他の言語の名前空間と変わらず扱えるのではないかと思います。

ただ今回のモジュールや特異メソッドを調べた時に色々苦労してみて、Rubyは用語や文法が独特の割にオフィシャルのドキュメントがそれほど充実していないので言語を理解する敷居を上げてしまっているのでは、とちょっと感じました。