💎

【Ruby 7日目】基本文法 - クラスとモジュール

に公開

はじめに

Rubyのクラスとモジュールについて、Ruby 3.4の仕様に基づいて詳しく解説します。基本文法の理解は、Rubyプログラミングの基礎となる重要な要素です。

この記事では、基本的な概念から実践的な使い方まで、具体的なコード例を交えて説明します。

基本概念

クラスとモジュールの主な特徴:

  • Rubyの動的な特性を活かした柔軟な記法
  • 直感的で読みやすい構文設計
  • 強力な表現力と簡潔性の両立

Rubyではクラスとモジュールを使用することで、より効率的で読みやすいコードを書くことができます。

基本的な使い方

クラスの基本

# クラスの定義
class User
  # クラス変数
  @@count = 0

  # 初期化メソッド
  def initialize(name, email)
    @name = name    # インスタンス変数
    @email = email
    @@count += 1
  end

  # インスタンスメソッド
  def greet
    "こんにちは、#{@name}です"
  end

  # クラスメソッド
  def self.count
    @@count
  end

  # アクセサ(ゲッター)
  def name
    @name
  end

  # アクセサ(セッター)
  def name=(value)
    @name = value
  end
end

# インスタンスの作成
user = User.new("Alice", "alice@example.com")
puts user.greet  # => "こんにちは、Aliceです"

user.name = "Bob"
puts user.greet  # => "こんにちは、Bobです"

puts User.count  # => 1

attr_accessor、attr_reader、attr_writer

class User
  # ゲッターとセッターを自動生成
  attr_accessor :name, :email

  # ゲッターのみ
  attr_reader :id

  # セッターのみ
  attr_writer :password

  def initialize(id, name, email)
    @id = id
    @name = name
    @email = email
  end
end

user = User.new(1, "Alice", "alice@example.com")
puts user.name       # => "Alice"
user.email = "new@example.com"
puts user.email      # => "new@example.com"
puts user.id         # => 1
# user.id = 2        # => エラー(セッターがない)

継承

# 親クラス
class Animal
  def initialize(name)
    @name = name
  end

  def speak
    "..."
  end

  def introduce
    "私は#{@name}です"
  end
end

# 子クラス
class Dog < Animal
  def speak
    "ワンワン!"
  end

  # 親のメソッドを呼び出す
  def introduce
    "#{super}、犬です"
  end
end

class Cat < Animal
  def speak
    "ニャー!"
  end
end

dog = Dog.new("ポチ")
puts dog.speak      # => "ワンワン!"
puts dog.introduce  # => "私はポチです、犬です"

cat = Cat.new("タマ")
puts cat.speak      # => "ニャー!"

モジュールの基本

# モジュールの定義
module Greetable
  def greet
    "こんにちは!"
  end

  def farewell
    "さようなら!"
  end
end

# モジュールのインクルード
class User
  include Greetable

  def initialize(name)
    @name = name
  end
end

user = User.new("Alice")
puts user.greet     # => "こんにちは!"
puts user.farewell  # => "さようなら!"

モジュール:名前空間

# 名前空間として使用
module MyApp
  module Models
    class User
      def initialize(name)
        @name = name
      end
    end
  end

  module Services
    class UserService
      def self.create(name)
        Models::User.new(name)
      end
    end
  end
end

# 使用例
user = MyApp::Models::User.new("Alice")
user2 = MyApp::Services::UserService.create("Bob")

include、extend、prepend

module Loggable
  def log(message)
    puts "[LOG] #{message}"
  end
end

# include: インスタンスメソッドとして追加
class Service1
  include Loggable

  def process
    log("処理開始")
  end
end

Service1.new.process  # => [LOG] 処理開始

# extend: クラスメソッドとして追加
class Service2
  extend Loggable
end

Service2.log("クラスメソッド")  # => [LOG] クラスメソッド

# prepend: メソッドチェーンの最初に挿入
module Wrapper
  def greet
    "【#{super}】"
  end
end

class User
  prepend Wrapper

  def greet
    "こんにちは"
  end
end

puts User.new.greet  # => "【こんにちは】"

基本的な使い方を理解したら、次は実践的なユースケースを見ていきましょう。

よくあるユースケース

ケース1: モデルクラス(ActiveRecord風)

実務でよく使われるモデルパターンです。

class User
  attr_accessor :name, :email, :age
  attr_reader :id, :created_at

  @@users = []

  def initialize(name:, email:, age:)
    @id = (@@users.size + 1)
    @name = name
    @email = email
    @age = age
    @created_at = Time.now
    @@users << self
  end

  # クラスメソッド
  def self.all
    @@users
  end

  def self.find(id)
    @@users.find { |user| user.id == id }
  end

  def self.find_by_email(email)
    @@users.find { |user| user.email == email }
  end

  # インスタンスメソッド
  def adult?
    age >= 20
  end

  def to_s
    "User ##{id}: #{name} (#{email})"
  end
end

# 使用例
alice = User.new(name: "Alice", email: "alice@example.com", age: 30)
bob = User.new(name: "Bob", email: "bob@example.com", age: 25)

puts User.all.map(&:name)  # => ["Alice", "Bob"]
puts User.find(1)          # => User #1: Alice (alice@example.com)
puts alice.adult?          # => true

ケース2: サービスクラス(ビジネスロジック)

複雑な処理をカプセル化するパターンです。

class UserRegistrationService
  def initialize(params)
    @params = params
    @errors = []
  end

  def call
    return failure unless valid?

    user = create_user
    send_welcome_email(user)
    notify_admins(user)

    success(user)
  end

  private

  def valid?
    if @params[:email].nil? || @params[:email].empty?
      @errors << "メールアドレスは必須です"
    end

    if @params[:password].nil? || @params[:password].length < 8
      @errors << "パスワードは8文字以上必要です"
    end

    @errors.empty?
  end

  def create_user
    puts "ユーザーを作成中: #{@params[:email]}"
    # User.create(@params)
    { id: 1, email: @params[:email] }
  end

  def send_welcome_email(user)
    puts "ウェルカムメールを送信: #{user[:email]}"
  end

  def notify_admins(user)
    puts "管理者に通知: 新規ユーザー #{user[:email]}"
  end

  def success(user)
    { success: true, user: user }
  end

  def failure
    { success: false, errors: @errors }
  end
end

# 使用例
service = UserRegistrationService.new(
  email: "new@example.com",
  password: "password123"
)

result = service.call
puts result

ケース3: Concern(共通機能の切り出し)

複数のクラスで共有する機能をモジュール化するパターンです。

# Timestampable: 作成日時・更新日時を管理
module Timestampable
  def self.included(base)
    base.class_eval do
      attr_reader :created_at, :updated_at

      alias_method :initialize_without_timestamps, :initialize

      def initialize(*args, **kwargs)
        initialize_without_timestamps(*args, **kwargs)
        @created_at = Time.now
        @updated_at = Time.now
      end
    end
  end

  def touch
    @updated_at = Time.now
  end
end

# Validatable: バリデーション機能
module Validatable
  def valid?
    validate.empty?
  end

  def validate
    []  # サブクラスでオーバーライド
  end

  def errors
    validate
  end
end

# 実際のモデルクラス
class Article
  include Timestampable
  include Validatable

  attr_accessor :title, :body
  @@articles = []

  def initialize(title:, body:)
    @title = title
    @body = body
    @@articles << self
  end

  def self.all
    @@articles
  end

  def validate
    errors = []
    errors << "タイトルは必須です" if title.nil? || title.empty?
    errors << "本文は必須です" if body.nil? || body.empty?
    errors
  end

  def to_s
    "#{title}: #{body[0..50]}"
  end
end

# 使用例
article = Article.new(title: "Ruby入門", body: "Rubyの基本を学びます")
puts article.created_at
puts article.valid?

注意点とベストプラクティス

注意点

  1. クラス変数の共有に注意
class Parent
  @@count = 0

  def self.increment
    @@count += 1
  end

  def self.count
    @@count
  end
end

class Child < Parent
end

Parent.increment  # @@count = 1
Child.increment   # @@count = 2(共有されている!)
puts Parent.count # => 2
puts Child.count  # => 2

# 解決策: クラスインスタンス変数を使う
class BetterParent
  @count = 0

  class << self
    attr_accessor :count
  end

  def self.increment
    @count += 1
  end
end
  1. 継承の深さに注意
# BAD: 深い継承階層
class Animal
end
class Mammal < Animal
end
class Carnivore < Mammal
end

# GOOD: コンポジション(モジュール活用)
module Walkable
end
module Carnivorous
end

class Cat < Animal
  include Walkable
  include Carnivorous
end
  1. private/protected の適切な使用
class BankAccount
  def initialize(balance)
    @balance = balance
  end

  def transfer(amount, to_account)
    return false if amount > @balance

    withdraw(amount)
    to_account.deposit(amount)
    true
  end

  protected

  def deposit(amount)
    @balance += amount
  end

  private

  def withdraw(amount)
    @balance -= amount
  end
end

ベストプラクティス

  1. 単一責任の原則(SRP)
  2. 依存性の注入(DI)
  3. 適切な命名規則

Ruby 3.4での改善点

  • YJIT最適化: メソッド呼び出しが高速化
  • メモリ効率: クラス定義のメモリ使用量が削減
  • エラーメッセージ: NoMethodErrorがより詳細に

まとめ

この記事では、クラスとモジュールについて以下の内容を学びました:

  • クラスとモジュールの基本概念と重要性
  • 基本的な使い方と構文
  • 実践的なユースケースとパターン
  • 注意点とベストプラクティス
  • Ruby 3.4での改善点

クラスとモジュールを理解することで、より効率的で保守性の高いRubyコードを書けるようになります。

参考資料

Discussion