⚙️

思い出したい人のためのRuby入門 Module編

に公開

はじめに

この記事は、あとから振り返ったときに、なるべく短い時間で内容を復習できるようにするために書かれています。

丁寧な説明記事は、長い時間の中でたくさんの方が作ってくださっているので、この記事では丁寧さよりも、簡潔さと網羅性を優先します。

Class編はこちら
https://zenn.dev/dev_zacker/articles/cf264ccafc52a5

誰のための記事か

  • 過去にRubyを学習したが、忘れてしまって思い出したい人

誰のためではないか

  • Rubyを初めて学ぶ人

Module 概要

Moduleでやりたいこと

  • 多重継承せずに機能を共有したい
  • 名前の重複を避けたい

Moduleの用途

  • 複数のクラスで機能を共有(Mixin)
  • 名前空間
  • モジュール単体で使う
  • アプリで唯一の値にしたい(シングルトン)

ModuleとClass、どっちをつかう?

Moduleを使うとき

  • 複数のクラスで共通の機能(振る舞い)を持たせたい
    • Moduleでインスタンス変数を「持っているような」書き方もできますが、振る舞いだけを持たせるように設計するべきです。
  • is-a関係(継承)が成り立たない
  • 名前空間を作りたい
    • Railsではディレクトリの階層構造に合わせて名前空間が定義されています。

Classを使うとき

  • 同じ振る舞いを持ちながら、異なるデータを持つオブジェクト(インスタンス)を作りたい
  • is-a関係(継承)が成り立つ
  • データと振る舞いをカプセル化したい
    • データとそのデータを操作するメソッドをまとめて、外部から直接アクセスできないようにすること

Moduleの定義

module Greeting
  def hello
    "Hello!"
  end
end

Mixin

  • 複数のクラスで機能を共有する
  • c.f. クラスは一つのスーパークラスしか持てない

include

  • インスタンスメソッドとして追加される
class User
  include Greeting
end
user = User.new
puts user.hello # Hello!

ダッグタイピング

ダックタイピングは「アヒルのように歩き、アヒルのように鳴くならば、それはアヒルである」という考え方で、オブジェクトの型よりも、オブジェクトが持つメソッドによって振る舞いを決定するという概念です。

ざっくりいうと、静的型付けのように型(関数やクラスを使う人が持っているべき機能の定義)がカッチリ決まっていなくても、動かすときに動けばええでしょというイメージです。

Rubyではダックタイピングが広く使われています。

moduleは、includeするclassが特定のメソッドを持っていることを前提に書くことができます。

module Greeter
  def greeting
    # module 内で name を定義していなくても
    # include した class がメソッドを持っていることを前提に呼び出す
    "My name is #{name}"
  end
end

class User
  include Greeter
  
  def initialize(name)
    @name = name
  end
  
  def name
    @name
  end
end
alice = User.new("Alice")
puts alice.greeting # My name is Alice

extend

  • クラスメソッドとして追加される
class User
  extend Greeting
end
puts User.hello # Hello!

名前空間

module Admin
  class User
    def admin?
      true
    end
  end
end

module Guest
  class User
    def admin?
      false
    end
  end
end
admin_user = Admin::User.new
puts admin_user.admin? # true

guest_user = Guest::User.new
puts guest_user.admin? # false

Module単体で使えるようにする

メソッド module_function

module Greeting
  module_function

  def hello
    "Hello!"
  end
end
puts Greeting.hello # Hello!

モジュール自身のメソッドとしてだけでなく
includeしたクラスのインスタンスメソッドとしても利用することができます。

class User
  include Greeting

  def greeting
    hello # privateメソッドとして呼び出せる
  end
end
user = User.new
puts user.greeting # Hello!
puts user.hello # private method 'hello' called for an instance of User

特異メソッド(Singleton Method)

特異メソッドとは、特定のオブジェクトに対してのみ定義するメソッドです。

Moduleの特異メソッドは単なる関数の集まりを作りたいという用途で利用します。

module Logger
  def self.debug(message)
    puts "[DEBUG] #{message}"
  end
  
  def self.error(message)
    puts "[ERROR] #{message}"
  end
end
Logger.debug("message") # [DEBUG] message
Logger.error("message") # [ERROR] message

class << self としても定義できます。

module Logger
  def self.debug(message)
    puts "[DEBUG] #{message}"
  end
  
  def self.error(message)
    puts "[ERROR] #{message}"
  end
end

特異メソッドはmodule_functionと異なり、includeしても使えません。
そのため、includeもしたいときはmodule_function、単純にmoduleのみに属するメソッドを定義したいときは特異メソッドという使い分けになりそうです。

定数

module Config
  API_VERSION = "v1"
  BASE_URL = "https://api.example.com"
end
puts Config::API_VERSION # v1
puts Config::BASE_URL # https://api.example.com

:: 名前空間演算子

名前空間の区切りとして使用します。

module Admin
  class User
    ROLE = "admin"
  end
end
puts Admin::User::ROLE # admin
user = Admin::User.new

トップレベルを指定する

::クラス名でトップレベルにあるクラスを参照できます。

モジュール内のクラスがトップレベルの同名のクラスを参照するときに使います。

class User
  def self.hello
    "Hello from User"
  end
end

module Admin
  class User
    def self.hello
      "Hello from Admin::User"
    end
    
    def self.call_top_level
      ::User.hello  # トップレベルのUserを指定
    end
  end
end
``
```rb
puts Admin::User.hello # Hello from Admin::User
puts Admin::User.call_top_level # Hello from User

.:: 、どっちを使う?

.でも::でも呼び出せますが、階層構造や名前空間を表現するときは::を、メソッド呼び出しをするときには.を使うようです。

  • メソッド呼び出し: .を使う

    class User
        def self.hello
          "Hello!"
        end
    end
    
  • 定数アクセス: ::を使う

    module Config
      API_VERSION = "v1"
    end
    
    puts Config::API_VERSION # v1
    
  • クラスやモジュールの指定: ::を使う

    module App
      module Models
        class User
        end
      end
    end
    
    user = App::Models::User.new
    

参考文献

Discussion