基礎

Rubyのモジュール入門|Mixinと名前空間の使い方を徹底解説

モジュール(Module)はメソッドや定数をまとめるための仕組みです。クラスと似ていますが、インスタンスを作成できないという違いがあります。モジュールは主に「Mixin(ミックスイン)」によるクラスへの機能追加と、「名前空間」による名前の衝突防止の2つの用途で使われます。

Mixin(ミックスイン)

モジュールのメソッドをincludeでクラスに取り込むことをMixinと呼びます。Rubyは単一継承ですが、Mixinにより複数の機能をクラスに追加できます。

Ruby
module Greetable
  def greet
    puts "こんにちは、#{name}です!"
  end
end

module Printable
  def print_info
    puts "=== 情報 ==="
    puts to_s
    puts "============"
  end
end

class User
  include Greetable
  include Printable

  attr_reader :name, :age

  def initialize(name, age)
    @name = name
    @age = age
  end

  def to_s
    "#{@name}(#{@age}歳)"
  end
end

user = User.new('太郎', 25)
user.greet        # Greetableモジュールのメソッド
user.print_info   # Printableモジュールのメソッド
実行結果
こんにちは、太郎です!
=== 情報 ===
太郎(25歳)
============

include GreetableGreetableモジュールのメソッドがUserクラスのインスタンスメソッドとして使えるようになります。複数のモジュールをincludeでき、これがRubyにおける多重継承の代替手段です。

名前空間としてのモジュール

モジュールを名前空間として使うと、クラス名の衝突を防げます。

Ruby
module Admin
  class User
    def initialize(name)
      @name = name
      @role = '管理者'
    end

    def to_s
      "#{@name}(#{@role})"
    end
  end
end

module Customer
  class User
    def initialize(name)
      @name = name
      @role = '顧客'
    end

    def to_s
      "#{@name}(#{@role})"
    end
  end
end

admin = Admin::User.new('管理太郎')
customer = Customer::User.new('顧客花子')

puts admin
puts customer
実行結果
管理太郎(管理者)
顧客花子(顧客)

同じUserというクラス名でも、Admin::UserCustomer::Userとして区別できます。大規模なプロジェクトで名前の衝突を避けるために重要なテクニックです。

extendでクラスメソッドを追加

includeはインスタンスメソッドを追加しますが、extendはクラスメソッドとして追加します。

Ruby
module ClassInfo
  def description
    "#{name}クラス(#{instance_methods(false).length}個のメソッド)"
  end
end

class Product
  extend ClassInfo

  attr_reader :name, :price

  def initialize(name, price)
    @name = name
    @price = price
  end

  def info
    "#{@name}: #{@price}円"
  end
end

puts Product.description  # クラスメソッドとして呼べる
実行結果
Productクラス(2個のメソッド)

実践的な使い方

Ruby
# 共通機能をモジュールで定義
module Validatable
  def validate_presence(field, value)
    if value.nil? || value.to_s.strip.empty?
      puts "エラー: #{field}は必須です"
      false
    else
      true
    end
  end
end

module Loggable
  def log(message)
    puts "[#{self.class.name}] #{message}"
  end
end

class Order
  include Validatable
  include Loggable

  attr_reader :product, :quantity

  def initialize(product, quantity)
    @product = product
    @quantity = quantity
  end

  def process
    return unless validate_presence('商品名', @product)
    return unless validate_presence('数量', @quantity)

    log("注文処理開始: #{@product} x #{@quantity}")
    log("注文処理完了")
  end
end

order1 = Order.new('ノートPC', 2)
order1.process

puts '---'

order2 = Order.new('', 1)
order2.process
実行結果
[Order] 注文処理開始: ノートPC x 2
[Order] 注文処理完了
---
エラー: 商品名は必須です
includeとextendの使い分け

includeはモジュールのメソッドをインスタンスメソッドとして追加します。extendはクラスメソッド(または特定のオブジェクトの特異メソッド)として追加します。用途に応じて使い分けましょう。

モジュールはインスタンスを作れない

モジュールはクラスと違いnewでインスタンスを作成できません。モジュールは機能の「部品」であり、単独で使うものではなく、クラスにinclude/extendして使います。

まとめ

  • モジュールはメソッドや定数をまとめる仕組み(インスタンスは作れない)
  • includeでモジュールのメソッドをインスタンスメソッドとして追加(Mixin)
  • extendでクラスメソッドとして追加
  • 名前空間として使い、クラス名の衝突を防止できる
  • Mixinは単一継承のRubyで多重継承の代替手段となる