💎
【Ruby 48日目】オブジェクト指向 - initializeメソッド
はじめに
Rubyのinitializeメソッドについて、Ruby 3.4の仕様に基づいて詳しく解説します。
この記事では、基本的な概念から実践的な使い方まで、具体的なコード例を交えて説明します。
基本概念
initializeメソッドは、オブジェクトの初期化を行う特別なメソッドです:
- コンストラクタ - newメソッド呼び出し時に自動的に実行される
- プライベートメソッド - 外部から直接呼び出すことはできない
- 初期化処理 - インスタンス変数の設定やセットアップ処理を行う
- 引数の受け取り - newに渡された引数がそのまま渡される
initializeメソッドを理解することで、オブジェクトの適切な初期化が可能になります。
基本的な使い方
シンプルなinitialize
class Person
def initialize(name, age)
@name = name
@age = age
end
def introduce
"I am #{@name}, #{@age} years old"
end
end
person = Person.new("Alice", 25)
puts person.introduce #=> I am Alice, 25 years old
デフォルト値を持つinitialize
class Product
def initialize(name, price = 0, stock = 0)
@name = name
@price = price
@stock = stock
end
def display
"#{@name}: ¥#{@price} (在庫: #{@stock})"
end
end
product1 = Product.new("ノートPC", 80000, 10)
puts product1.display #=> ノートPC: ¥80000 (在庫: 10)
product2 = Product.new("マウス", 1500)
puts product2.display #=> マウス: ¥1500 (在庫: 0)
product3 = Product.new("キーボード")
puts product3.display #=> キーボード: ¥0 (在庫: 0)
キーワード引数を使ったinitialize
class User
def initialize(name:, email:, age: nil, role: :member)
@name = name
@email = email
@age = age
@role = role
end
def to_s
details = "Name: #{@name}, Email: #{@email}"
details += ", Age: #{@age}" if @age
details += ", Role: #{@role}"
details
end
end
user1 = User.new(name: "Alice", email: "alice@example.com", age: 25, role: :admin)
puts user1 #=> Name: Alice, Email: alice@example.com, Age: 25, Role: admin
user2 = User.new(name: "Bob", email: "bob@example.com")
puts user2 #=> Name: Bob, Email: bob@example.com, Role: member
ブロックを受け取るinitialize
class Configuration
def initialize
@settings = {}
yield self if block_given?
end
def set(key, value)
@settings[key] = value
end
def get(key)
@settings[key]
end
def to_h
@settings
end
end
config = Configuration.new do |c|
c.set(:host, "localhost")
c.set(:port, 3000)
c.set(:debug, true)
end
puts config.to_h.inspect #=> {:host=>"localhost", :port=>3000, :debug=>true}
継承とsuperの使用
class Animal
def initialize(name)
@name = name
puts "Animal initialized: #{@name}"
end
def speak
"..."
end
end
class Dog < Animal
def initialize(name, breed)
super(name) # 親クラスのinitializeを呼ぶ
@breed = breed
puts "Dog initialized: #{@breed}"
end
def speak
"Woof! I'm #{@name}, a #{@breed}"
end
end
dog = Dog.new("Pochi", "Shiba")
#=> Animal initialized: Pochi
# Dog initialized: Shiba
puts dog.speak #=> Woof! I'm Pochi, a Shiba
バリデーション付きinitialize
class BankAccount
attr_reader :account_number, :balance
def initialize(account_number, initial_balance = 0)
raise ArgumentError, "Account number must be a string" unless account_number.is_a?(String)
raise ArgumentError, "Account number cannot be empty" if account_number.empty?
raise ArgumentError, "Initial balance cannot be negative" if initial_balance < 0
@account_number = account_number
@balance = initial_balance
@created_at = Time.now
end
def display
"Account: #{@account_number}, Balance: ¥#{@balance}, Created: #{@created_at}"
end
end
account = BankAccount.new("ACC-001", 10000)
puts account.display
#=> Account: ACC-001, Balance: ¥10000, Created: 2025-11-24 10:00:00
# エラーケース
begin
BankAccount.new("", 5000)
rescue ArgumentError => e
puts "Error: #{e.message}" #=> Error: Account number cannot be empty
end
begin
BankAccount.new("ACC-002", -1000)
rescue ArgumentError => e
puts "Error: #{e.message}" #=> Error: Initial balance cannot be negative
end
よくあるユースケース
ケース1: 依存性の注入
他のオブジェクトへの依存を初期化時に注入します。
class Logger
def log(message)
puts "[#{Time.now}] #{message}"
end
end
class DatabaseLogger
def log(message)
puts "[DB LOG] #{message}"
end
end
class UserService
def initialize(logger = Logger.new)
@logger = logger
end
def create_user(name, email)
# ユーザー作成処理
user = { name: name, email: email, id: rand(1000) }
@logger.log("User created: #{name} (#{email})")
user
end
def delete_user(id)
@logger.log("User deleted: ID #{id}")
end
end
# 通常のロガーを使用
service1 = UserService.new
service1.create_user("Alice", "alice@example.com")
#=> [2025-11-24 10:00:00] User created: Alice (alice@example.com)
# データベースロガーを注入
service2 = UserService.new(DatabaseLogger.new)
service2.create_user("Bob", "bob@example.com")
#=> [DB LOG] User created: Bob (bob@example.com)
ケース2: ファクトリーパターンとの組み合わせ
initializeをprivateにして、ファクトリーメソッド経由でのみ生成できるようにします。
class Connection
attr_reader :type, :config
private_class_method :new
def initialize(type, config)
@type = type
@config = config
@connected = false
end
def self.database(host, port, database)
config = {
host: host,
port: port,
database: database
}
new(:database, config)
end
def self.api(endpoint, api_key)
config = {
endpoint: endpoint,
api_key: api_key
}
new(:api, config)
end
def self.file(path)
config = { path: path }
new(:file, config)
end
def connect
@connected = true
"Connected to #{@type}: #{@config.inspect}"
end
end
db_conn = Connection.database("localhost", 5432, "myapp")
puts db_conn.connect
#=> Connected to database: {:host=>"localhost", :port=>5432, :database=>"myapp"}
api_conn = Connection.api("https://api.example.com", "secret-key")
puts api_conn.connect
#=> Connected to api: {:endpoint=>"https://api.example.com", :api_key=>"secret-key"}
# 直接newは呼べない
# Connection.new(:test, {}) # NoMethodError
ケース3: 遅延初期化
必要になるまで初期化を遅延させます。
class DataProcessor
def initialize(source)
@source = source
@data = nil
@processed = nil
end
def data
@data ||= load_data
end
def processed_data
@processed ||= process_data
end
private
def load_data
puts "Loading data from #{@source}..."
sleep(0.5) # データ読み込みをシミュレート
["item1", "item2", "item3"]
end
def process_data
puts "Processing data..."
sleep(0.5) # データ処理をシミュレート
data.map(&:upcase)
end
end
processor = DataProcessor.new("database")
puts "Processor created" #=> Processor created(まだデータは読み込まれていない)
puts processor.data.inspect
#=> Loading data from database...
# ["item1", "item2", "item3"]
puts processor.data.inspect # 再度呼んでもキャッシュから返される
#=> ["item1", "item2", "item3"](Loading...は表示されない)
puts processor.processed_data.inspect
#=> Processing data...
# ["ITEM1", "ITEM2", "ITEM3"]
ケース4: 複雑なオブジェクト構築
ビルダーパターンと組み合わせて複雑なオブジェクトを構築します。
class EmailBuilder
attr_reader :email
def initialize
@email = Email.new
end
def from(address)
@email.from = address
self
end
def to(address)
@email.to = address
self
end
def subject(text)
@email.subject = text
self
end
def body(text)
@email.body = text
self
end
def attach(filename)
@email.attachments << filename
self
end
def build
raise "Sender is required" if @email.from.nil?
raise "Recipient is required" if @email.to.nil?
raise "Subject is required" if @email.subject.nil?
@email
end
end
class Email
attr_accessor :from, :to, :subject, :body
attr_reader :attachments
def initialize
@from = nil
@to = nil
@subject = nil
@body = ""
@attachments = []
end
def send
puts "Sending email:"
puts " From: #{@from}"
puts " To: #{@to}"
puts " Subject: #{@subject}"
puts " Body: #{@body}"
puts " Attachments: #{@attachments.join(', ')}" unless @attachments.empty?
"Email sent successfully"
end
end
email = EmailBuilder.new
.from("sender@example.com")
.to("recipient@example.com")
.subject("Meeting Tomorrow")
.body("Let's meet at 10 AM")
.attach("agenda.pdf")
.attach("slides.pptx")
.build
puts email.send
#=> Sending email:
# From: sender@example.com
# To: recipient@example.com
# Subject: Meeting Tomorrow
# Body: Let's meet at 10 AM
# Attachments: agenda.pdf, slides.pptx
# Email sent successfully
ケース5: 継承チェーンでの初期化
複数階層の継承で適切にinitializeを連鎖させます。
class Vehicle
attr_reader :make, :model
def initialize(make, model)
@make = make
@model = model
puts "Vehicle initialized: #{@make} #{@model}"
end
def info
"#{@make} #{@model}"
end
end
class Car < Vehicle
attr_reader :doors
def initialize(make, model, doors = 4)
super(make, model)
@doors = doors
puts "Car initialized: #{@doors} doors"
end
def info
"#{super}, #{@doors} doors"
end
end
class ElectricCar < Car
attr_reader :battery_capacity
def initialize(make, model, doors = 4, battery_capacity:)
super(make, model, doors)
@battery_capacity = battery_capacity
puts "ElectricCar initialized: #{@battery_capacity}kWh battery"
end
def info
"#{super}, #{@battery_capacity}kWh battery"
end
def range
@battery_capacity * 5 # 簡易的な航続距離計算
end
end
tesla = ElectricCar.new("Tesla", "Model 3", 4, battery_capacity: 75)
#=> Vehicle initialized: Tesla Model 3
# Car initialized: 4 doors
# ElectricCar initialized: 75kWh battery
puts tesla.info #=> Tesla Model 3, 4 doors, 75kWh battery
puts "Range: #{tesla.range}km" #=> Range: 375km
注意点とベストプラクティス
注意点
- initializeは常にprivate
class Example
def initialize(value)
@value = value
end
end
obj = Example.new(10)
# initializeは直接呼べない
# obj.initialize(20) # NoMethodError: private method `initialize' called
- superの引数に注意
class Parent
def initialize(a, b)
@a = a
@b = b
end
end
class Child1 < Parent
def initialize(a, b, c)
super(a, b) # 明示的に引数を指定
@c = c
end
end
class Child2 < Parent
def initialize(a, b, c)
# BAD: superは全引数を渡そうとする
# super # ArgumentError: wrong number of arguments (given 3, expected 2)
super(a, b) # GOOD: 必要な引数のみ渡す
@c = c
end
end
child = Child1.new(1, 2, 3) # OK
- 循環参照に注意
# BAD: 循環参照が発生する可能性
class Node
attr_accessor :value, :parent
def initialize(value, parent = nil)
@value = value
@parent = parent
parent.children << self if parent
end
end
# GOOD: 循環参照を避ける
class BetterNode
attr_accessor :value
attr_reader :children
def initialize(value)
@value = value
@children = []
end
def add_child(child)
@children << child
end
end
ベストプラクティス
- 必須パラメータとオプションパラメータを明確に
# GOOD: キーワード引数で必須とオプションを明確に
class User
def initialize(name:, email:, age: nil, role: :member)
@name = name # 必須
@email = email # 必須
@age = age # オプション
@role = role # オプション(デフォルト値あり)
end
end
# 必須パラメータがないとエラー
# User.new(age: 25) # ArgumentError: missing keywords: :name, :email
- バリデーションは早期に
# GOOD: initializeでバリデーション
class Rectangle
def initialize(width, height)
raise ArgumentError, "Width must be positive" unless width > 0
raise ArgumentError, "Height must be positive" unless height > 0
@width = width
@height = height
end
def area
@width * @height
end
end
# エラーはオブジェクト作成時に発生
# rect = Rectangle.new(-5, 10) # ArgumentError
- 複雑な初期化は別メソッドに分離
# GOOD: 初期化処理を分離
class Application
def initialize(config_file)
@config = load_config(config_file)
@database = setup_database
@cache = setup_cache
@logger = setup_logger
end
private
def load_config(file)
# 設定ファイルの読み込み
{ host: "localhost", port: 3000 }
end
def setup_database
# データベース接続のセットアップ
"DB Connection"
end
def setup_cache
# キャッシュのセットアップ
{}
end
def setup_logger
# ロガーのセットアップ
Logger.new(STDOUT)
end
end
Ruby 3.4での改善点
- Prismパーサーによる最適化 - initializeメソッドの解析が高速化
- YJITの最適化 - オブジェクト生成とinitialize呼び出しが効率的に
- メモリ管理の改善 - 初期化時のメモリ割り当てが最適化
- エラーメッセージの改善 - パラメータエラーがより明確に
# Ruby 3.4では、大量のオブジェクト生成が高速
class Point
def initialize(x, y)
@x = x
@y = y
end
def distance_from_origin
Math.sqrt(@x**2 + @y**2)
end
end
# 大量のオブジェクト生成が最適化されている
points = 100000.times.map { |i| Point.new(i, i * 2) }
まとめ
この記事では、initializeメソッドについて以下の内容を学びました:
- 基本概念と重要性 - コンストラクタ、プライベートメソッド、初期化処理
- 基本的な使い方と構文 - デフォルト値、キーワード引数、ブロック、super
- 実践的なユースケース - 依存性注入、ファクトリー、遅延初期化、ビルダー、継承チェーン
- 注意点とベストプラクティス - super引数、バリデーション、メソッド分離
initializeメソッドを適切に使うことで、オブジェクトの初期化が確実に行えます。
Discussion