Ruby クラスとオブジェクト
オブジェクト指向プログラミング(Object-Oriented Programming、略称 OOP)は、モダンなソフトウェア開発の基盤です。コードをオーガナイズ(Organize)し、現実世界のエンティティ(Entity)をモデリング(Modeling)するための強力なプログラミングパラダイム(Paradigm)を提供します。
本章では、RubyにおけるOOPの最もコアな概念であるクラス(Class)とオブジェクト(Object)についてディープダイブ(Deep Dive)します。これらの概念を理解することは、複雑でメンテナンス性の高いアプリケーションをビルド(Build)するためのキー(Key)となります。この基盤を固めることで、後続のチャプターで継承(Inheritance)やモジュール(Module)といった高度なOOPトピックへスムーズにステップアップできます。
1. クラス (Class) の理解
クラスは、オブジェクトをクリエイト(Create)するためのブループリント(Blueprint)またはテンプレート(Template)です。そのクラスのオブジェクトが持つアトリビュート(Attribute:データ)とビヘイビア(Behavior:メソッド)をディファイン(Define)します。クラスをクッキーの型(Cookie Cutter)だと想像してみてください。型がクッキーの形状を決定し、焼き上げられた個々のクッキーが独立したオブジェクト(インスタンス)となります。
1.1 クラスのディファイン (Define)
Rubyでは、class キーワード(Keyword)を使用してクラスをディファインでき、その直後にクラス名(必ず大文字でスタートする必要があります)を記述し、最後に end キーワードでクラスのコードブロックをクローズします。
class Dog
# クラスのコードブロックをここに記述します
endこのコードは Dog という名前の空のクラスをディファインしています。現在は何もアクションを実行できませんが、これは完全に合法(Legal)なクラスのディファインです。
1.2 アトリビュート (Attribute)
アトリビュートはオブジェクトに関連付けられたデータであり、オブジェクトのステート(State)をリプレゼン(Represent)します。例えば、Dog(犬)オブジェクトは、name(名前)、breed(品種)、age(年齢)などのアトリビュートを持つ可能性があります。
Rubyはアトリビュートをディファインするための極めて便利なアプローチを提供しています。それが attr_accessor、attr_reader、attr_writer というアクセサ(Accessor)です。
- attr_accessor:アトリビュートに対するゲッター(Getter:読み取り)とセッター(Setter:書き込み)のメソッド(Method)を同時に生成します。これにより、該当アトリビュートの値を読み取り、かつモディファイ(Modify)することが可能になります。
- attr_reader:アトリビュートに対するゲッター(Getter)メソッドのみを生成します。値の読み取りは許可されますが、ダイレクトにモディファイすることはできません。
- attr_writer:アトリビュートに対するセッター(Setter)メソッドのみを生成します。値のモディファイは許可されますが、ダイレクトに読み取ることはできません。
class Dog
attr_accessor :name, :breed, :age
endこのコードは Dog クラスに対して name、breed、age という3つのアトリビュートをディファインしています。attr_accessor を使用しているため、任意の Dog オブジェクトのこれら3つのアトリビュートをいつでも読み取り、モディファイすることが可能です。
1.3 メソッド (Method)
メソッドはオブジェクトのビヘイビア(Behavior)をディファインします。これらはオブジェクトのデータ(アトリビュート)を操作したり、その他のアクションを実行したりするファンクション(Function)です。例えば、Dog オブジェクトは、bark(吠える)、eat(食べる)、sleep(寝る)などのメソッドを持つ可能性があります。
class Dog
attr_accessor :name, :breed, :age
def bark
puts "ワンワン!"
end
def eat(food)
puts "#{@name} は #{food} を食べています。"
end
def display_info
puts "名前: #{@name}, 品種: #{@breed}, 年齢: #{@age}"
end
endこのコードは Dog クラスに3つのメソッドをアド(Add)しています。
- bark:コンソール(Console)に "ワンワン!" と出力します。
- eat:
food(食べ物)というパラメータ(Parameter)をレシーブ(Receive)し、この犬が何かを食べていることを示すメッセージを出力します。これはメソッドがどのようにパラメータをレシーブするかをデモンストレーションしています。 - display_info:犬の名前、品種、年齢を出力します。これはメソッドがオブジェクト自身のアトリビュートにどのようにアクセスし、ユース(Use)するかを示しています。
1.4 initialize メソッド (Constructor)
initialize は、該当クラスの新しいオブジェクトをクリエイトする際にRubyが自動的にコール(Call)する特別なメソッドです。通常、オブジェクトのアトリビュートを初期化(Initialize)するために使用されます。他のプログラミング言語では、これは一般的にクラスのコンストラクタ(Constructor)と呼ばれます。
class Dog
attr_accessor :name, :breed, :age
def initialize(name, breed, age)
@name = name
@breed = breed
@age = age
end
def bark
puts "ワンワン!"
end
def eat(food)
puts "#{@name} は #{food} を食べています。"
end
def display_info
puts "名前: #{@name}, 品種: #{@breed}, 年齢: #{@age}"
end
endこのコードブロックにおいて:
initializeメソッドはname、breed、ageという3つのパラメータをレシーブします。initializeメソッドの内部で、これらのパラメータを対応するインスタンス変数(Instance Variables)である@name、@breed、@ageにアサイン(Assign)します。@ シンボルのプレフィックス(Prefix)を持つ変数がインスタンス変数であり、これらがオブジェクト自身に属するエクスクルーシブ(Exclusive)なデータであることを意味します。
2. オブジェクト (Object) のクリエイト
オブジェクトはクラスのインスタンス(Instance)です。クラスによってディファインされたブループリントに基づいてビルドされた具体的なエンティティです。
2.1 クラスのインスタンス化 (Instantiation)
オブジェクトをクリエイトするには、該当クラス上で new メソッドをコールする必要があります。new をコールすると、Rubyはバックグラウンドで自動的に initialize メソッド(存在する場合)をコールし、オブジェクトの初期ステート(Initial State)をセットアップ(Setup)します。
# 上記の Dog クラスの定義から続きます
dog1 = Dog.new("Buddy", "ゴールデンレトリバー", 3)
dog2 = Dog.new("Lucy", "ラブラドールレトリバー", 5)このコードは2つの Dog オブジェクトをクリエイトしています。
dog1は "Buddy" という名前のゴールデンレトリバーで、年齢は3歳です。dog2は "Lucy" という名前のラブラドールレトリバーで、年齢は5歳です。
各オブジェクトは独立したアトリビュートのセットを保持しており、これらのアトリビュートはクリエイト時に initialize メソッドによって初期化が完了しています。
2.2 アトリビュートへのアクセスとメソッドのコール
ドット記法(Dot Notation:.)を使用して、オブジェクトのアトリビュートにアクセス(事前に attr_accessor や attr_reader でクリエイトした読み取りメソッドを経由します)したり、オブジェクトのメソッドをコールしたりすることができます。
アトリビュートへのアクセス:
puts dog1.name # 出力: Buddy
puts dog2.breed # 出力: ラブラドールレトリバーメソッドのコール:
dog1.bark # 出力: ワンワン!
dog2.eat("ドッグフード") # 出力: Lucy は ドッグフード を食べています。
dog1.display_info # 出力: 名前: Buddy, 品種: ゴールデンレトリバー, 年齢: 3各オブジェクトは自身のコンテキスト(Context)内でメソッドを実行(Execute)し、自身に属するアトリビュートのバリュー(Value)を使用します。
3. 総合実践:BankAccount クラス
より実際のビジネスシナリオに近い例を見てみましょう。BankAccount(銀行口座)クラスです。この例は、クラスとオブジェクトを活用して、複雑なビヘイビアを持つ現実世界のエンティティをどのようにモデリングするかを示します。
class BankAccount
# 口座番号と残高は外部に対してリードオンリー(読み取り専用)
attr_reader :account_number, :balance
# 口座名義人は読み取りおよびモディファイが可能
attr_accessor :account_holder_name
def initialize(account_holder_name, account_number, initial_balance)
@account_holder_name = account_holder_name
@account_number = account_number
@balance = initial_balance
end
def deposit(amount)
if amount > 0
@balance += amount
puts "$#{amount} をデポジット(入金)しました。現在の残高: $#{@balance}"
else
puts "無効なデポジット金額です。"
end
end
def withdraw(amount)
if amount > 0 && amount <= @balance
@balance -= amount
puts "$#{amount} をウィズドロー(出金)しました。現在の残高: $#{@balance}"
elsif amount <= 0
puts "無効なウィズドロー金額です。"
else
puts "残高が不足しています。"
end
end
def display_balance
puts "#{@account_holder_name} の口座残高は: $#{@balance} です"
end
end
# BankAccount オブジェクトをクリエイト
account1 = BankAccount.new("Alice Smith", "1234567890", 1000)
# アトリビュートへのアクセス
puts account1.account_holder_name # 出力: Alice Smith
puts account1.account_number # 出力: 1234567890
# メソッドのコール
account1.deposit(500) # 出力: $500 をデポジット(入金)しました。現在の残高: $1500
account1.withdraw(200) # 出力: $200 をウィズドロー(出金)しました。現在の残高: $1300
account1.display_balance # 出力: Alice Smith の口座残高は: $1300 です
account1.withdraw(2000) # 出力: 残高が不足しています。コアポイントの解析:
- attr_reader と attr_accessor: コード内では
account_numberとbalanceに対してattr_readerを使用しています。なぜなら、クラスの外部からは読み取りのみを許可し、自由に改ざんされることを防ぐためです。account_holder_nameは(改名などによって)変更される可能性があるため、attr_accessorを使用しています。これはアトリビュートに対するアクセス制御(Access Control)を体現しており、OOPのコア原則の一つです。 - initialize メソッド: これは
BankAccountオブジェクトの初期ステートのセットアップを担当し、アカウント名、アカウント番号、初期残高をパラメータとしてレシーブします。 - deposit と withdraw メソッド: これら2つのメソッドは、入出金のビジネスロジックをカプセル化(Encapsulation)しています。内部にはデータバリデーション(Data Validation)が含まれており、金額が合法であることを担保しています。出力メッセージも、オペレーションの結果と最新の残高を明確にフィードバック(Feedback)します。
- 残高のカプセル化 (Encapsulation): 残高 (
@balance) はオブジェクトの内部にプロテクト(Protect)されています。外部から残高をモディファイする唯一のアプローチは、オブジェクトが提供するdepositおよびwithdrawメソッドを経由することであり、これがデータの完全性(Integrity)とセキュリティ(Security)を根本的に保証します。