モジュールには名前空間とミックスイン(Mix-in)の2つの機能があります。 ここではミックスインについて説明します。
モジュールの定義
モジュールの定義とクラスの定義は似ています。 クラスの定義にはclassキーワードを使いますが、モジュール定義ではその代わりにmoduleキーワードを使います。
module Abc def abc print "abcdefg\n" end end
モジュール名はAbc
です。
モジュール名は定数で、最初の文字は大文字でなければいけません。
モジュールはクラスと違い、インスタンスを作ることはできません。
もし、Abc.new
としてインスタンスを作ろうとするとエラーになります。
インクルード
先程の例ではインスタンスメソッドabc
をモジュールAbc
の中で定義しました。
しかし、モジュールではインスタンスを作れないのですから、このメソッドは使いようがないように見えます。
メソッドを使うには、includeメソッドでモジュールを取り込む必要があります。
include Abc abc
実行すると
abcdefg
と画面に現れます。 つまり、モジュールのメソッドabcを実行できたのです。
モジュールをインクルードすることにより、モジュールのメソッドを引き継ぐことができます。 通常はクラス定義の中でモジュールをインクルードしてメソッドを引き継ぎます。
class Xyz include Abc def xyz abc end end x = Xyz.new x.abc x.xyz
これを実行すると、クラスXyzでモジュールAbcがインクルードされることにより、モジュールのメソッドabcがクラスXyzのインスタンスをレシーバとして呼ぶことができるようになります。 プログラムを実行するとabcdefが2つ表示されます。
abcdef abcdef
- Xyzクラスのインスタンス
x
を生成 abc
はXyz
のインスタンスメソッドになったから、x.abc
によってabcdefが表示される- インスタンスメソッド
xyz
の中でインスタンスメソッドabc
を呼び出せる。 def xyz〜endでは、selfが指すのはxyzが呼び出されたときのレシーバ(インスタンス)である。 そして、メソッド定義の中でメソッド呼び出し(abc
)があり、そのレシーバが省略されたときは、selfをレシーバとする。 プログラムリストの最後の行で、x.xyz
が呼び出されたとき、その中で呼び出されたabc
のレシーバはx
になる(selfはxになっている)
なお、includeはメソッド定義の中では使えません。 クラス定義の中で、メソッド定義の外でインクルードしてください。
ミックスイン
これまでのところをまとめておきましょう。
このことから、モジュールの目的(のひとつミックスイン)はクラスのインスタンスメソッドを提供することです。 しかしここで次のような疑問が浮かびます。 最初からクラス内でメソッドを定義すればインクルードなど必要ないのに、なぜわざわざモジュールのメソッドを取り込むのでしょうか?
モジュールは特定のクラスのためにメソッドを定義しているのではなく、複数のクラスに提供するためにメソッドを定義しているのです。 例えば、Comparableという組み込みのモジュールがあります。
- Comparableは
<=>
メソッドをもとに、==
、>
、>=
、<
、<=
、between?
、clamp
というメソッドを定義している - Comparableをインクルードするクラスには
<=>
メソッドが定義されていなければならない
Comparableは比較可能なオブジェクトである整数、実数、文字列などに適用されています。
また、ユーザが比較可能なクラスを作るとき、<=>
メソッドを定義し、Comparableをインクルードするだけで、==
などの7つのメソッドが使えるようになります。
例として、江ノ電の駅のオブジェクトを作り、Comparableをインクルードしてみましょう。 駅には駅番号があり、藤沢のEN01から鎌倉のEN15までとなっています。 この駅番号の大小で駅の大小を決めることにします。
class Enoden include Comparable attr_reader :station_number, :name def initialize station_number, name @station_number = station_number @name = name end def <=>(other) self.station_number.slice(2..3).to_i <=> other.station_number.slice(2..3).to_i end def to_s "#{@station_number}: #{@name}" end end station_number = (1..15).map{|i| sprintf("EN%02d", i)} name = "藤沢 石上 柳小路 鵠沼 湘南海岸公園 江ノ島 腰越 鎌倉高校前 七里ヶ浜 稲村ヶ崎 極楽寺 長谷 由比ヶ浜 和田塚 鎌倉".split(/ /) enoshima = Enoden.new(station_number[5], name[5]) inamuragasaki = Enoden.new(station_number[9], name[9]) kamakura = Enoden.new(station_number[14], name[14]) print enoshima, "\n" print inamuragasaki, "\n" print enoshima < inamuragasaki, "\n" print [kamakura, enoshima, inamuragasaki].sort.map{|a| a.to_s}, "\n"
これを実行すると次のようになります。
EN06: 江ノ島 EN10: 稲村ヶ崎 true ["EN06: 江ノ島", "EN10: 稲村ヶ崎", "EN15: 鎌倉"]
プログラム中のattr_reader
メソッドは読み出しのみサポートするインスタンス変数を定義します。
次のプログラムと同等です。
def station_number @station_number end def name @name end
プログラムを説明します。
- クラスEnodenはモジュールComparableをインクルードする
- attr_readerメソッドによって、読み出しのみ可のインスタンス変数
@station_number
と@name
を定義 - オブジェクトを生成する時に、引数に駅番号、駅名を与える
- 大小比較
<=>
は駅番号文字列の3〜4文字目を(sliceは0から数えるので2..3
となっている)整数に直して比較 to_s
メソッドは駅番号と駅名を文字列として返す- 駅番号の文字列の配列を作成
- 駅名の文字列の配列を作成
- 江ノ島、稲村ヶ崎、鎌倉のEnodenオブジェクトを作成
- enoshimaとinamuragasakiを(to_sを使って)プリント
- enoshimaとinamuragasakiの大小比較。稲村ヶ崎の駅番号の方が大きいのでtrueが返る
- kamakura, enoshima, inamuragasakiの配列を作り、ソート。
<=>
が定義してあるので、ソートが可能。 to_sで駅番号、駅名の文字列に直して表示
今回の例では江ノ電だけを対象にしましたが、一般に日本の駅には駅番号(駅ナンバリング)が用いられるようになってきました。 駅ナンバリングは3字以内の英字(路線)と番号でできています。 これから、Enodenクラスをより一般的な駅を表すStationクラスに格上げすることも可能だと思います。 駅の比較は(1)英字をアルファベットで大小をつけ(2)番号で大小をつけるという2段階で実現できます
さて、具体的にComparableモジュールがクラスにインクルードされ、その実態(オブジェクト)を手に入れるのを見てきました。 比較可能なクラスは様々あり、それらすべてにComparableモジュールが適用できます。 このように、クラス共通のメソッドを定義したモジュールを、各クラスに適用することをミックスインといいます。
Comparableは既存のモジュールですが、ユーザが独自にモジュールを作り、複数のクラスに適用することもできます。 また、Comparable以外にもEnumerableという非常に有用なモジュールがあります。 このモジュールはeachメソッドのあるクラスに適用できます。 eachメソッドを作るにはブロック付きメソッドの定義を知らなければならないので、今回は解説しませんでしたが、別の記事で取り上げられればと思っています。