💎
【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?
注意点とベストプラクティス
注意点
- クラス変数の共有に注意
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
- 継承の深さに注意
# 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
- 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
ベストプラクティス
- 単一責任の原則(SRP)
- 依存性の注入(DI)
- 適切な命名規則
Ruby 3.4での改善点
- YJIT最適化: メソッド呼び出しが高速化
- メモリ効率: クラス定義のメモリ使用量が削減
- エラーメッセージ: NoMethodErrorがより詳細に
まとめ
この記事では、クラスとモジュールについて以下の内容を学びました:
- クラスとモジュールの基本概念と重要性
- 基本的な使い方と構文
- 実践的なユースケースとパターン
- 注意点とベストプラクティス
- Ruby 3.4での改善点
クラスとモジュールを理解することで、より効率的で保守性の高いRubyコードを書けるようになります。
Discussion