LoginSignup
0
0

More than 5 years have passed since last update.

[Ruby入門] 11. クラスを拡張する① モジュールのお話

Last updated at Posted at 2017-04-22

>> 連載の目次は こちら!

クラスの話の続き。
ここからは、Rubyにおけるクラスの機能拡張の方法を整理してみたいと思う。
前々回に継承の話をしたが、Rubyの特徴として、継承以外にも様々なクラスの拡張方法が用意されている。
それらの話のひとつめとして、まずはモジュールについて整理してみる。

■ モジュールの概要

  • ある種の機能を実現するための一連のメソッドを提供する、まさにモジュール
  • クラスに似ているが、インスタンス化できない、変数は持たない(定数は持てる)、などの特徴がある
  • クラスに include/extend(後述)することで、モジュールの持つ機能(メソッド群)を、クラスに取り込むことができる
  • 複数のモジュールを include/extend できるので、多重継承的な仕組みを実現できる
  • クラスに取り込まずに、モジュール.メソッドで実行したり、関数のように実行したりする方法もある
  • そのほか、クラスをカテゴライズするためにも利用できる(名前空間の提供)

■ 基本的な定義方法と、クラスへの取り込み方法

module Drummer
  def take_rhythm
    puts "DoDoDan!!"
  end
end

module Bassist
  def slap_on_bass
    puts "ButchBatch!!"
  end
end

module Guitarist
  def play_emotional
    puts "GaGaGaa!!"
  end
end

module Vocalist
  def sing
    puts "LaLaLa♬"
  end
end

class Band

  # include でインスタンスメソッドとして取り込む(Mix-in)
  include Guitarist
  include Vocalist

  # extend でクラスメソッドとして取り込む
  extend Drummer
  extend Bassist

  def play
    # インスタンスメソッドとして取り込んだメソッドをクラスの中で実行
    play_emotional
    sing
  end

  def groove
    # クラスメソッドとして取り込んだメソッドをクラスの中で実行
    Band.take_rhythm
    Band.slap_on_bass
  end

end

# モジュールを include/extend したクラスの中で、モジュールのメソッドを実行してみる
band = Band.new
band.groove
  # DoDoDan!!
  # ButchBatch!!
band.play
  # GaGaGaa!!
  # LaLaLa♬

# モジュールを include/extend したクラスや、そのインスタンスから、モジュールのメソッドを実行してみる
Band.take_rhythm  # entend で取り込んだメソッド(クラスメソッド)なので、BandのClassオブジェクトから実行
  # DoDoDan!!
band.sing         # include で取り込んだメソッド(インスタンスメソッド)なので、Bandのインスタンスから実行
  # LaLaLa♬

■ 同名のメソッドがあった場合の優先順位

  • モジュールのメソッドよりクラスで定義されているメソッドが優先
  • モジュール同士で同名のメソッドがあったら、あとから取り込んだメソッドが優先
  • インスタンスメソッドとクラスメソッドなら、所属先が違うので重複しても大丈夫
module M1
  def same1; puts "M1" end
  def same2; puts "M1" end
  def same3; puts "M1" end
  def same4; puts "M1" end
end
module M2
  def same2; puts "M2" end
  def same3; puts "M2" end
  def same4; puts "M2" end
end
module M3
  def same3; puts "M3" end
  def same4; puts "M3" end
end
class Cls
  include M1
  include M2
  extend M3
  def same4; puts "Cls" end
end

Cls.new.same1  # M1   M1にしか定義されていない
Cls.new.same2  # M2   M2があとからincludeされている
Cls.new.same3  # M2   インスタンスメソッドとしてはM2が最後にincludeされている
Cls.same3      # M3   クラスメソッドとしてはM3が最後にextendされている
Cls.new.same4  # Cls  インスタンスメソッドとしてはClsで定義されている
Cls.same4      # M3   クラスメソッドとしてはM3が最後にextendされている

■ モジュールのメソッドを直接実行する

クラスに取り込まなくても、モジュールから直接メソッドを実行したり、関数のように実行したりすることもできる

module GuitarShop
  def repair
    puts "brush frets!!"
  end

  def purchase
    puts "please sell it to me!!"
  end

  # repair メソッドだけ、「モジュール関数」として提供する
  module_function :repair
end

class MyShop
  include GuitarShop

  def refresh
    repair
    puts "brush body!!"
  end
end

# モジュール名を明示してメソッドを実行
GuitarShop.repair     # これは module_function で モジュール関数として提供されているからOK
GuitarShop.purchase   # これはNG! モジュール関数として提供されていない。

# モジュールをincludeし、関数のように実行する
include GuitarShop
repair    # このやり方なら、
purchase  #     どちらのメソッドも実行できる(モジュール関数として提供されていなくても大丈夫)

# モジュールをincludeしたクラスから実行してみる
myShop = MyShop.new
myShop.refresh  # これは、モジュール関数になっていないのでOK
myShop.repair   # これはNG! モジュール関数に指定されているメソッドは private になるので、インスタンスから呼べない

■ モジュールをクラスの名前空間として利用する

同名のクラスを定義すると、Rubyだと既存クラスの定義が変更されてしまう
名前空間を使って、クラスをカテゴライズすれば安心。

module Http
  class Input
    def read; puts "from request" end 
  end
  class Output
    def write; puts "to browser" end 
  end
end

module Cli
  class Input
    def read; puts "from shell" end 
  end
  class Output
    def write; puts "to shell" end 
  end
end

# 名前空間内のクラスは、モジュール名::クラス名 でアクセスする
Http::Input.new.read   # from request
Cli::Output.new.write  # to shell

■ Rubyの既存モジュールの利用例

● Comparable モジュールで大小比較可能なクラスを定義する

class Bass

  # Comparable モジュールを搭載
  # Bassオブジェクトは大小を比較することができるよー
  include Comparable

  attr_reader :scale

  def initialize(scale)
    @scale = scale
  end

  # <=>(other) メソッドの実装が必須
  # Bassオブジェクトに比較系のメソッド( > とかの比較演算子)が指定されたら、このメソッドが呼ばれるよー
  def <=>(other)
    # このクラスではネックのスケール長で大小を比較することにするよー
    @scale <=> other.scale
  end

end

mustang  = Bass.new(30) # ムスタングってショートスケールで可愛いよねー
jazzbass = Bass.new(34) # 王道ジャズベはやっぱりロングスケールっしょ
p (jazzbass > mustang)  # true ムスタングよりジャズベのがおっきいな

● Enumerable モジュールで繰り返し処理が可能なクラスを定義する

  • 配列にあるような繰り返し系の処理(each_with_index とか map とか include? とか、配列の章で紹介したようなメソッド群)を実行できるようになる
  • 繰り返し系の処理は、すべて each メソッド経由で呼ばれるため、eachメソッドの実装が必須。
class JukeBox

  # Comparable モジュールを搭載
  # JukeBoxオブジェクトは繰り返し処理ができるよー
  include Enumerable

  def initialize(songs)
    @songs = songs
  end

  # eachメソッドの実装が必須
  # each_with_index とか map とか、繰り返し系の処理はこのメソッド経由で実行される
  def each
    # このクラスでは、ジュークボックスが保持している歌たちをループすることにするよー
    @songs.each do |item|
      yield(item)  # ここで、each_with_index とか all とか map とかを呼んでいる
    end
  end
end

jb = JukeBox.new(["whats going on", "dock of the bay", "i want you back"])

# JukeBox のオブジェクトから、配列で使うような繰り返し系の処理を実行できるようになる
p jb.include?("dock of the bay")  # true

jb.map { |item| puts "sing " + item }
  # sing whats going on
  # sing dock of the bay
  # sing i want you back

jb.each_with_index do |item, i|
  puts "#{i+1}. #{item}"
end
  # 1. whats going on
  # 2. dock of the bay
  # 3. i want you back
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0