Ruby 入門

Ruby クラスのインスタンス化

クラスのインスタンス(通常「オブジェクト」と呼ばれます)をクリエイトすることは、オブジェクト指向プログラミング(OOP)におけるコアコンセプトです。このプロセスを通じてこそ、クラスがディファイン(Define)した「ブループリント(Blueprint)」に命を吹き込み、具体的なアトリビュート(Attribute)とビヘイビア(Behavior)を持つ実体としてコード内で操作可能になります。

1. インスタンス化の基礎

インスタンス化とは、クラスからオブジェクトをクリエイトするアクションです。Rubyでは、これは通常 new メソッドを使用することで実行されます。このメソッドは Object クラス(Rubyのすべてのクラスの共通の祖先)から継承(Inherit)されています。クラスに対して new をコール(Call)すると、Rubyは新しいオブジェクトのためにメモリ上のスペースをアロケート(Allocate)し、その後 initialize メソッド(定義されている場合)をコールして、該当オブジェクトの初期ステート(Initial state)をセットアップします。

シンプルな例を見てみましょう:

class Dog
  def initialize(name, breed)
    @name = name
    @breed = breed
  end

  def bark
    puts "ワンワン!"
  end
end

# Dog クラスのインスタンスをクリエイト
my_dog = Dog.new("Buddy", "ゴールデンレトリバー")

# puts my_dog.name # 注意: attr_reader が設定されていないため、ここでダイレクトにアクセスするとエラーが発生します
my_dog.bark        # 出力: ワンワン!

この例において、Dog.new("Buddy", "ゴールデンレトリバー") は新しい Dog オブジェクトをクリエイトし、それを my_dog 変数(Variable)にアサイン(Assign)しています。initialize メソッドは "Buddy""ゴールデンレトリバー" というパラメータ(Parameter)をレシーブし、それらを使用して @name@breed という2つのインスタンス変数(Instance Variable)をセットアップします。

1.1 new メソッドの内部メカニズム

new はクラスメソッド(Class Method)(つまり、クラスのインスタンス上ではなく、クラス自体に対して直接コールされるメソッド)であり、ゼロから新しいオブジェクトをクリエイトする責任を担っています。バックグラウンドでは、new は以下のキーとなるステップを実行します:

  1. 新しいオブジェクトにメモリをアロケートする。
  2. 新しいオブジェクト上で initialize メソッド(存在する場合)をコールし、new にパッシング(Passing)されたすべてのパラメータをそのままフォワード(Forward)する。
  3. クリエイトおよび初期化が完了したオブジェクトをリターン(Return)する。

日常的な開発において、new メソッドを自らオーバーライド(Override)する必要性は極めて稀です。Rubyが提供するデフォルトの実装で大多数のシナリオに対応可能です。非常に高度なデザイン(例えば、クラスのインスタンスが1つしかクリエイトされないことを保証するシングルトンパターン(Singleton Pattern)を実装する場合など)においてのみ、これに触れることになります。

1.2 initialize メソッド (コンストラクタ)

initialize はインスタンスメソッド(Instance Method)です。new を使用して新しいオブジェクトをクリエイトするたびに、自動的にコールされます。その最優先のタスクはオブジェクトの初期ステートをセットアップすることであり、これは通常、インスタンス変数に値をアサインすることで実現されます。これはクラスのコンストラクタ(Constructor)として機能します。

  • 目的: オブジェクトの初期ステートをセットアップする。
  • パラメータ: initialize は任意の数のパラメータをレシーブできます。これらは new をコールする際にパッシングされるものです。
  • インスタンス変数: initialize の内部では、通常インスタンス変数(@ で始まる変数)に値をアサインします。これらの変数はオブジェクトのプライベート(Private)なデータを保持し、同クラス内の他のインスタンスメソッドからアクセス可能です。
class Rectangle
  def initialize(width, height)
    @width = width
    @height = height
  end

  def area
    @width * @height
  end
end

# Rectangle (矩形) オブジェクトをクリエイト
my_rectangle = Rectangle.new(10, 5)

puts my_rectangle.area # 出力: 50

この例では、initializewidthheight をレシーブし、それらをそれぞれ @width@height インスタンス変数にアサインします。その後、area メソッドはこれらのインスタンス変数を使用して矩形の面積を計算(Calculate)することができます。

2. インスタンス化時のパラメータのパッシング

クラスのインスタンスをクリエイトする際、new メソッドにパラメータをパッシングすることができ、それらのパラメータはシームレスに initialize メソッドへと転送されます。これにより、クリエイトするオブジェクトごとに独自の初期ステートをカスタマイズすることが可能になります。

2.1 キーワード引数 (Keyword Arguments)

Rubyはキーワード引数(Keyword Arguments)の使用を強くサポートしています。複数のパラメータを持つメソッドをハンドリングする際、これはコードのリーダビリティ(Readability)とメンテナンス性(Maintainability)を劇的に向上させます。

class Book
  def initialize(title:, author:, pages:)
    @title = title
    @author = author
    @pages = pages
  end

  def to_s
    "タイトル: #{@title}, 著者: #{@author}, ページ数: #{@pages}"
  end
end

# キーワード引数を使用して Book オブジェクトをクリエイト
my_book = Book.new(title: "Ruby プログラミング言語", author: "David Flanagan", pages: 600)

puts my_book.to_s # 出力: タイトル: Ruby プログラミング言語, 著者: David Flanagan, ページ数: 600

キーワード引数を使用することで、どの値がどのパラメータにアサインされているかが一目瞭然となり、「ブラインドゲス(Blind guess)」を排除できます。

2.2 デフォルトパラメータ値

initialize メソッドにおいて、パラメータにデフォルト値(Default value)をプロバイド(Provide)することも可能です。オブジェクトのクリエイト時にこれらのパラメータが提供されなかった場合、システムは自動的にこれらの合理的なデフォルトセッティングを採用します。

class Circle
  def initialize(radius: 1, color: "レッド")
    @radius = radius
    @color = color
  end

  def area
    Math::PI * @radius**2
  end

  def to_s
    "半径: #{@radius}, カラー: #{@color}"
  end
end

# デフォルト値を使用した Circle オブジェクトをクリエイト
my_circle = Circle.new
puts my_circle.to_s   # 出力: 半径: 1, カラー: レッド
puts my_circle.area   # 出力: 3.141592653589793

# カスタム値を使用した Circle オブジェクトをクリエイト
another_circle = Circle.new(radius: 5, color: "ブルー")
puts another_circle.to_s  # 出力: 半径: 5, カラー: ブルー
puts another_circle.area  # 出力: 78.53981633974483

ここでは、Circle のクリエイト時にパラメータがパッシングされない場合、radius はデフォルトで 1 に、color はデフォルトで "レッド" になります。

3. オブジェクトステートのアクセスとモディファイ (復習と深化)

クラスのインスタンスをクリエイトした後、頻繁にそのステート(つまり、インスタンス変数の値)にアクセスし、モディファイする必要があります。前のレッスンで述べたように、Rubyはこれをエレガントに処理するために attr_readerattr_writer、および attr_accessor を提供しています。

  • attr_reader: リードオンリー(Read-only)。ゲッター(Getter)メソッドをクリエイトします。
  • attr_writer: ライトオンリー(Write-only)。セッター(Setter)メソッドをクリエイトします。
  • attr_accessor: リード・ライト(Read/Write)のフルアクセス。ゲッターとセッターの両方のメソッドを同時にクリエイトします。
class Car
  attr_accessor :make, :model, :year
  
  def initialize(make, model, year)
    @make = make
    @model = model
    @year = year
  end
  
  def to_s
    "#{@year} #{@make} #{@model}"
  end
end

car = Car.new("トヨタ", "カムリ", 2020)
puts car.to_s # 出力: 2020 トヨタ カムリ

car.year = 2022 # attr_accessor によって自動生成されたセッターメソッドをユース
puts car.to_s   # 出力: 2022 トヨタ カムリ
puts car.make   # attr_accessor によって自動生成されたゲッターメソッドをユース (出力: トヨタ)

アーキテクチャの考察: attr_accessor は極めて高い利便性を提供しますが、クラスをデザインする際は、すべての変数を外部からのリード・ライトにエクスポーズ(Expose)すべきかどうかを慎重に検討する必要があります。時として、オブジェクト内部のステートの整合性を維持するために、特定の変数へのアクセス権限を制限する(例えば attr_reader のみを使用する、あるいは完全にプライベートにする)ことは非常に重要です。

4. 実践ケーススタディ:シンプルなターン制ゲームの構築

より実践的なコンテキスト(Context)において、クラスのインスタンスをどのようにクリエイトするかをデモンストレーションするために、非常にベーシックなゲームシナリオを構築してみましょう。

class Player
  attr_accessor :name, :health, :power

  def initialize(name, health: 100, power: 10)
    @name = name
    @health = health
    @power = power
  end

  def attack(enemy)
    damage = rand(1..@power)  # 与えるダメージは自身のパワー値に基づいてランダムに変動します
    enemy.health -= damage
    puts "#{@name} が #{enemy.name} をアタックし、#{damage} のダメージを与えた!"
    puts "#{enemy.name} の残りヘルス(HP): #{enemy.health}"
  end

  def is_alive?
    @health > 0
  end
end

# 2つの Player オブジェクトをインスタンス化
player1 = Player.new("勇者", health: 120, power: 15)
player2 = Player.new("魔王", health: 80, power: 20)

puts "--- バトルスタート ---"

# シンプルなデスマッチをシミュレート
while player1.is_alive? && player2.is_alive?
  player1.attack(player2)
  break unless player2.is_alive? # 魔王が倒れた場合、ループ(Loop)を早期ブレイク(Break)する
  
  player2.attack(player1)
end

puts "--- バトルエンド ---"

if player1.is_alive?
  puts "#{player1.name} が最終的な勝利を収めた!"
else
  puts "#{player2.name} が最終的な勝利を収めた!"
end

この例は、Player クラスのインスタンスをクリエイトし、それぞれ異なる初期値でそれらを初期化し、その後メソッドをコールしてバトルをシミュレート(Simulate)する方法を完璧に示しています。これは、現実(あるいは仮想)世界におけるインタラクション(Interaction)をモデリングする際、インスタンスが果たすコアな役割を鮮やかに表現しています。