Ruby 入門

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_accessorattr_readerattr_writer というアクセサ(Accessor)です。

  • attr_accessor:アトリビュートに対するゲッター(Getter:読み取り)とセッター(Setter:書き込み)のメソッド(Method)を同時に生成します。これにより、該当アトリビュートの値を読み取り、かつモディファイ(Modify)することが可能になります。
  • attr_reader:アトリビュートに対するゲッター(Getter)メソッドのみを生成します。値の読み取りは許可されますが、ダイレクトにモディファイすることはできません。
  • attr_writer:アトリビュートに対するセッター(Setter)メソッドのみを生成します。値のモディファイは許可されますが、ダイレクトに読み取ることはできません。
class Dog
  attr_accessor :name, :breed, :age
end

このコードは Dog クラスに対して namebreedage という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)に "ワンワン!" と出力します。
  • eatfood(食べ物)というパラメータ(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 メソッドは namebreedage という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_accessorattr_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_numberbalance に対して 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)を根本的に保証します。