Ruby/Railsで学ぶオブジェクト指向入門 クラスとオブジェクトって何ぞや
はじめに
こんにちは!本記事は、Ruby/Railsを使ってオブジェクト指向プログラミングの基礎について記載しています。本記事はオブジェクト指向編の続きとなります。
クラス定義に関して、サンプルコードをもとに記載しております!!
2. クラスとオブジェクトの基礎
2.1 クラスの定義とオブジェクトの生成
Rubyでクラスを定義するにはclass
キーワードを使います。クラス名は大文字で始める必要があります。
class Person
# クラスの内容(メソッドや属性など)
end
クラスからオブジェクト(インスタンス)を生成するには、new
メソッドを使います。
person = Person.new
より実用的な例を見てみましょう:
class User
attr_accessor :name, :email
def initialize(name, email)
@name = name
@email = email
end
def greeting
"こんにちは、#{@name}です!"
end
end
# オブジェクトの生成
user1 = User.new("田中", "tanaka@example.com")
user2 = User.new("佐藤", "sato@example.com")
# オブジェクトのメソッドとプロパティにアクセス
puts user1.name # => 田中
puts user1.greeting # => こんにちは、田中です!
puts user2.name # => 佐藤
puts user2.greeting # => こんにちは、佐藤です!
# 属性の変更
user1.name = "田中修正"
puts user1.greeting # => こんにちは、田中修正です!
2.2 フィールド(プロパティ)とメソッドの基本
クラスには主に2種類の構成要素があります:
- インスタンス変数(フィールド/プロパティ):オブジェクトのデータを保持します
- メソッド:オブジェクトの振る舞いや機能を定義します
インスタンス変数
Rubyでは、インスタンス変数は@
で始まります。これらの変数はオブジェクトのインスタンスごとに独立した値を持ちます。
class Product
def initialize(name, price)
@name = name # @nameはインスタンス変数
@price = price # @priceはインスタンス変数
end
end
デフォルトでは、インスタンス変数は外部から直接アクセスできません。アクセスするには、ゲッターメソッドとセッターメソッドを定義するか、Rubyのattr_accessor
、attr_reader
、attr_writer
といったマクロを使います。
class Product
# name属性の読み書きメソッドを生成
attr_accessor :name
# price属性の読み取り専用メソッドを生成
attr_reader :price
def initialize(name, price)
@name = name
@price = price
end
# priceを変更するカスタムメソッド
def price=(new_price)
if new_price >= 0 # 負の価格は許可しない
@price = new_price
else
puts "価格は0以上である必要があります"
end
end
end
product = Product.new("本", 1500)
puts product.name # => 本
puts product.price # => 1500
product.name = "参考書"
puts product.name # => 参考書
product.price = 2000
puts product.price # => 2000
product.price = -500 # => 価格は0以上である必要があります
puts product.price # => 2000(変更されていない)
メソッド
メソッドはオブジェクトの振る舞いを定義します。Rubyでは、メソッドの最後に評価された式が自動的に戻り値になります(return
は省略可能です)。
class Calculator
def add(a, b)
a + b # 最後の式が戻り値になる
end
def subtract(a, b)
a - b
end
def multiply(a, b)
return a * b # returnを明示的に使うこともできる
end
def divide(a, b)
if b == 0
return "ゼロ除算はできません"
end
a / b.to_f # 整数除算を避けるためにto_fで浮動小数点に変換
end
end
calc = Calculator.new
puts calc.add(5, 3) # => 8
puts calc.subtract(10, 4) # => 6
puts calc.multiply(2, 6) # => 12
puts calc.divide(10, 2) # => 5.0
puts calc.divide(10, 0) # => ゼロ除算はできません
2.3 コンストラクタの役割
コンストラクタとは、オブジェクトが生成されるときに自動的に呼び出される特別なメソッドです。Rubyでは、コンストラクタはinitialize
メソッドとして定義します。
コンストラクタの主な役割:
- インスタンス変数の初期化
- オブジェクト生成時に必要な処理の実行
class Book
attr_reader :title, :author, :pages, :published_date
def initialize(title, author, pages, published_date = nil)
@title = title
@author = author
@pages = pages
@published_date = published_date || Time.now # 引数がnilの場合は現在時刻を使用
puts "「#{@title}」の本が作成されました" # オブジェクト生成時のメッセージ
end
def info
info_str = "タイトル: #{@title}, 著者: #{@author}, ページ数: #{@pages}"
if @published_date
info_str += ", 出版日: #{@published_date.strftime('%Y-%m-%d')}"
end
info_str
end
end
# コンストラクタに引数を渡してオブジェクトを生成
book1 = Book.new("Ruby入門", "山田太郎", 300)
# => 「Ruby入門」の本が作成されました
book2 = Book.new("Rails実践ガイド", "鈴木花子", 450, Time.new(2023, 5, 15))
# => 「Rails実践ガイド」の本が作成されました
puts book1.info
# => タイトル: Ruby入門, 著者: 山田太郎, ページ数: 300, 出版日: 2023-12-10
puts book2.info
# => タイトル: Rails実践ガイド, 著者: 鈴木花子, ページ数: 450, 出版日: 2023-05-15
コンストラクタに引数のデフォルト値を設定したり、条件分岐を入れたりすることで、オブジェクト生成時の柔軟性を高めることができます。
2.4 実践問題と解説(2問)
問題1: 銀行口座クラスの作成
銀行口座を表すクラスBankAccount
を作成してください。以下の機能を実装します:
- 口座番号、所有者名、残高を保持する
- 入金できる(
deposit
メソッド) - 出金できる(
withdraw
メソッド)- ただし残高が不足している場合は出金できない - 口座情報を表示できる(
display_info
メソッド) - 別の口座に送金できる(
transfer
メソッド)
解答:
class BankAccount
attr_reader :account_number, :owner_name, :balance
def initialize(account_number, owner_name, initial_balance = 0)
@account_number = account_number
@owner_name = owner_name
@balance = initial_balance
puts "#{owner_name}さんの口座(#{account_number})が作成されました。初期残高: #{initial_balance}円"
end
def deposit(amount)
if amount > 0
@balance += amount
puts "#{amount}円を入金しました。残高: #{@balance}円"
return true
else
puts "入金額は0より大きい値を指定してください"
return false
end
end
def withdraw(amount)
if amount <= 0
puts "出金額は0より大きい値を指定してください"
return false
elsif amount > @balance
puts "残高不足です。残高: #{@balance}円, 出金要求額: #{amount}円"
return false
else
@balance -= amount
puts "#{amount}円を出金しました。残高: #{@balance}円"
return true
end
end
def display_info
puts "口座情報: #{@account_number}, 所有者: #{@owner_name}, 残高: #{@balance}円"
end
def transfer(target_account, amount)
puts "#{target_account.owner_name}さんの口座(#{target_account.account_number})に#{amount}円送金します"
if withdraw(amount) # 自分の口座から出金
if target_account.deposit(amount) # 相手の口座に入金
puts "送金が完了しました"
return true
else
# 入金に失敗した場合、出金分を戻す
@balance += amount
puts "送金に失敗しました。出金分を返却します。残高: #{@balance}円"
return false
end
else
puts "出金できなかったため、送金に失敗しました"
return false
end
end
end
# 使用例
account1 = BankAccount.new("1234-5678", "山田太郎", 10000)
account2 = BankAccount.new("8765-4321", "鈴木花子", 5000)
account1.display_info
account2.display_info
account1.deposit(5000)
account1.withdraw(2000)
# 残高不足の場合
account2.withdraw(10000)
# 送金のテスト
account1.transfer(account2, 3000)
account1.display_info
account2.display_info
解説:
このBankAccount
クラスでは、以下のように銀行口座の機能を実装しています:
-
インスタンス変数:口座番号(
@account_number
)、所有者名(@owner_name
)、残高(@balance
)を保持します。 - コンストラクタ:口座の初期化を行い、初期残高のデフォルト値を0としています。
-
入金メソッド(
deposit
):金額が正の値であることを確認してから残高に加算します。 -
出金メソッド(
withdraw
):金額が正の値であり、残高が十分であることを確認してから残高から減算します。 -
情報表示メソッド(
display_info
):口座情報を表示します。 -
送金メソッド(
transfer
):自分の口座から出金して相手の口座に入金します。出金や入金に失敗した場合はエラーメッセージを表示します。
このクラスはカプセル化の原則に従い、口座残高(@balance
)を直接変更できないようにしています。代わりに、deposit
やwithdraw
などのメソッドを通じてのみ残高を変更できます。これにより、不正な操作(例:負の金額の入金)を防ぐことができます。
問題2: 商品在庫管理システムの作成
商品在庫を管理するシステムを作成してください。以下のクラスを実装します:
-
Product
クラス:商品を表す- 商品ID、名前、価格、カテゴリーを保持する
- 商品情報を表示できる
-
Inventory
クラス:商品在庫を管理する- 複数の商品とその在庫数を保持する
- 商品を追加できる(
add_product
メソッド) - 在庫を増やせる(
add_stock
メソッド) - 在庫を減らせる(
remove_stock
メソッド) - 在庫リストを表示できる(
display_inventory
メソッド) - カテゴリー別に在庫を表示できる(
display_by_category
メソッド)
解答:
class Product
attr_reader :id, :name, :price, :category
def initialize(id, name, price, category)
@id = id
@name = name
@price = price
@category = category
end
def display_info
puts "商品ID: #{@id}, 名前: #{@name}, 価格: #{@price}円, カテゴリー: #{@category}"
end
def price=(new_price)
if new_price >= 0
@price = new_price
puts "#{@name}の価格を#{@price}円に更新しました"
else
puts "価格は0以上である必要があります"
end
end
end
class Inventory
def initialize
@stock = {} # 商品IDをキー、在庫数を値とするハッシュ
@products = {} # 商品IDをキー、商品オブジェクトを値とするハッシュ
end
def add_product(product, initial_stock = 0)
if @products[product.id]
puts "ID: #{product.id}の商品は既に登録されています"
return false
else
@products[product.id] = product
@stock[product.id] = initial_stock
puts "#{product.name}を#{initial_stock}個追加しました"
return true
end
end
def add_stock(product_id, amount)
if !@products[product_id]
puts "ID: #{product_id}の商品は登録されていません"
return false
elsif amount <= 0
puts "追加する在庫数は1以上である必要があります"
return false
else
@stock[product_id] += amount
puts "#{@products[product_id].name}の在庫を#{amount}個追加しました。現在の在庫: #{@stock[product_id]}個"
return true
end
end
def remove_stock(product_id, amount)
if !@products[product_id]
puts "ID: #{product_id}の商品は登録されていません"
return false
elsif amount <= 0
puts "減らす在庫数は1以上である必要があります"
return false
elsif @stock[product_id] < amount
puts "在庫が足りません。現在の在庫: #{@stock[product_id]}個, 要求数: #{amount}個"
return false
else
@stock[product_id] -= amount
puts "#{@products[product_id].name}の在庫を#{amount}個減らしました。現在の在庫: #{@stock[product_id]}個"
return true
end
end
def display_inventory
puts "===== 在庫リスト ====="
if @products.empty?
puts "商品がありません"
else
@products.each do |id, product|
puts "#{product.name}: #{@stock[id]}個"
product.display_info
puts "---------------------"
end
end
end
def display_by_category(category)
puts "===== #{category}カテゴリーの在庫 ====="
category_products = @products.values.select { |product| product.category == category }
if category_products.empty?
puts "#{category}カテゴリーの商品はありません"
else
category_products.each do |product|
puts "#{product.name}: #{@stock[product.id]}個"
product.display_info
puts "---------------------"
end
end
end
end
# 使用例
inventory = Inventory.new
# 商品の作成
laptop = Product.new(1, "ノートパソコン", 80000, "電子機器")
phone = Product.new(2, "スマートフォン", 60000, "電子機器")
desk = Product.new(3, "デスク", 15000, "家具")
chair = Product.new(4, "椅子", 8000, "家具")
# 在庫に商品を追加
inventory.add_product(laptop, 5)
inventory.add_product(phone, 10)
inventory.add_product(desk, 3)
inventory.add_product(chair, 7)
# 在庫の表示
inventory.display_inventory
# 在庫の追加と削減
inventory.add_stock(1, 3)
inventory.remove_stock(2, 2)
# 在庫不足の場合
inventory.remove_stock(3, 5)
# カテゴリー別の表示
inventory.display_by_category("電子機器")
inventory.display_by_category("家具")
inventory.display_by_category("食品") # 存在しないカテゴリー
解説:
この在庫管理システムは、Product
クラスとInventory
クラスの2つのクラスで構成されています:
-
Productクラス:
- 商品の基本情報(ID、名前、価格、カテゴリー)を保持します。
-
display_info
メソッドで商品の詳細情報を表示します。 -
price=
メソッドで価格を更新できますが、不正な値(負の値)は設定できないようにしています。
-
Inventoryクラス:
- 商品とその在庫数を管理します。
- 2つのハッシュを使用して情報を管理しています:
-
@stock
:商品IDをキー、在庫数を値とするハッシュ -
@products
:商品IDをキー、商品オブジェクトを値とするハッシュ
-
-
add_product
メソッドで新しい商品を追加します。 -
add_stock
とremove_stock
メソッドで在庫数を増減します。 -
display_inventory
メソッドで全ての在庫を表示します。 -
display_by_category
メソッドでカテゴリー別に在庫を表示します。
このシステムでは、オブジェクト指向の考え方に沿って以下のような設計がされています:
- カプセル化:各クラスは必要なデータとメソッドを内部に持ち、外部から直接アクセスされるべきでない情報(例:
@stock
ハッシュ)は隠蔽されています。 - 責任の分離:商品に関する情報は
Product
クラスが、在庫管理はInventory
クラスが担当しています。 - データの検証:入力値が適切であるかどうかをメソッド内で検証しています(例:在庫数や価格が正の値であることなど)。
これにより、商品の追加、在庫の更新、在庫状況の表示などの操作が整理された形で実装されています。
終わりに
ここまでお読み頂きありがとうございます!それでは3章でお会いしましょう!
追記(2025年月4月10日)
本記事の3章目となるカプセル化についての記事を執筆しました!ぜひお読みくださいませ!!
Discussion