【Ruby】クラスとオブジェクト指向の基本を初心者向けに解説

Ruby

こんにちは、かつコーチです。

Rubyの学習を進めていくと、必ず「クラス」という言葉にぶつかります。

「なんとなくコピペで使えるけど、実は仕組みがよく分かっていない」という方も多いのではないでしょうか。

この記事では、Rubyにおけるクラスとオブジェクト指向の基本を、初心者の方向けに丁寧に解説します。

読み終える頃には、classを使った定義からinitializeattr_accessorまで、自分の手で書けるようになります。

クラスとオブジェクト指向とは?

オブジェクト指向とは何か

オブジェクト指向とは、データとそれを操作する処理を「モノ(オブジェクト)」としてひとまとめにして扱う考え方です。

たとえば「ユーザー」を扱うプログラムなら、名前や年齢といったデータと、あいさつをするといった処理を、1つの「ユーザーオブジェクト」としてまとめられます。

Rubyは「すべてがオブジェクトである」という思想を徹底している言語です。

数値や文字列、配列すら、すべてオブジェクトとして扱われます。

puts 1.class      # => Integer
puts "hello".class # => String
puts [1, 2].class  # => Array

このように、どんな値にも.classメソッドで確認できる「型」があり、それぞれがオブジェクトとして振る舞います。

クラスとインスタンスの関係

クラスとは、オブジェクトの「設計図」です。

その設計図から実際に作られたモノをインスタンスと呼びます。

「たい焼きの型(クラス)」と「実際に焼き上がったたい焼き(インスタンス)」の関係をイメージすると分かりやすいです。

同じ型からでも、あんこ入り・チョコ入りなど、中身が異なるたい焼きを作れますよね。

クラスも同様に、同じ設計図から、名前や年齢が異なる複数のインスタンスを作成できます。

基本の書き方:クラス定義の手順

手順1:classでクラスを定義する

Rubyではclassキーワードを使ってクラスを定義します。

クラス名は大文字始まりのキャメルケース(例:UserShoppingCart)にするのがルールです。

class User
end

user = User.new
puts user.class # => User

User.newとすることで、Userクラスのインスタンスを1つ作成できます。

このままでは何もデータを持たない空のオブジェクトです。

手順2:initializeでコンストラクタを定義する

インスタンスを作るときに、名前や年齢などの初期データを渡したい場合は、initializeメソッドを使います。

initializeコンストラクタと呼ばれ、newが呼ばれたときに自動的に実行される特別なメソッドです。

class User
  def initialize(name, age)
    @name = name
    @age = age
  end
end

user = User.new("かつ", 30)

User.new("かつ", 30)とすると、initializeの引数name"かつ"age30が渡されます。

手順3:インスタンス変数でデータを保持する

initializeの中に出てきた@name@ageは、インスタンス変数と呼ばれるものです。

先頭に@が付いた変数で、そのインスタンスの中でだけ使えるデータを保持します。

インスタンス変数は、そのクラス内のどのメソッドからでも参照できます。

class User
  def initialize(name, age)
    @name = name
    @age = age
  end

  def introduce
    "私は#{@name}です。#{@age}歳です。"
  end
end

user = User.new("かつ", 30)
puts user.introduce
# => 私は かつ です。30歳です。

introduceのように、クラスの中に定義したメソッドはインスタンスメソッドと呼ばれ、user.introduceのようにインスタンスから呼び出せます。

手順4:attr_accessorで外部からデータを扱う

ここで1つ問題があります。

インスタンス変数は、クラスの外から直接読み書きできません。

user = User.new("かつ", 30)
puts user.name
# => NoMethodError: undefined method `name' for #<User:0x00007f...>

わたしも最初にRubyを触ったとき、このNoMethodErrorにかなり戸惑いました。

@nameに値は入っているはずなのに、なぜ取り出せないんだろう」と、しばらく原因が分からなかった記憶があります。

この理由はシンプルで、インスタンス変数を読み書きするための専用メソッド(getter/setter)を自分で定義しないと、外部からはアクセスできないからです。

本来は以下のように、nameメソッド(getter)とname=メソッド(setter)を自分で書く必要があります。

class User
  def initialize(name, age)
    @name = name
    @age = age
  end

  def name
    @name
  end

  def name=(new_name)
    @name = new_name
  end
end

しかし、名前や年齢が増えるたびにこれを書くのは大変です。

そこでRubyには、この定型処理を自動生成してくれるattr_accessorというメソッドが用意されています。

class User
  attr_accessor :name, :age

  def initialize(name, age)
    @name = name
    @age = age
  end

  def introduce
    "私は#{@name}です。#{@age}歳です。"
  end
end

user = User.new("かつ", 30)
puts user.name       # => かつ
user.age = 31
puts user.introduce   # => 私は かつ です。31歳です。

attr_accessor :name, :ageと1行書くだけで、namename=ageage=の4つのメソッドが自動で定義されます。

読み取り専用にしたい場合はattr_reader、書き込み専用にしたい場合はattr_writerも用意されているので、用途に応じて使い分けましょう。

よくあるつまずきポイント・エラー対処

インスタンス変数とattr_accessorの混同

Before/Afterで整理しておきます。

❌ Before:attr_accessorを付け忘れて外部アクセスできない

class User
  def initialize(name)
    @name = name
  end
end

user = User.new("かつ")
puts user.name
# => NoMethodError: undefined method `name' for #<User:0x00007f9c8a0b0d18 @name="かつ">

先ほどのわたし自身のつまずきそのままの例です。

エラーメッセージをよく見ると、@name="かつ"とインスタンス変数には値が入っていることが分かります。

「データはあるのに取り出す手段がない」というのが、このエラーの正体です。

✅ After:attr_accessorでgetterを用意する

class User
  attr_accessor :name

  def initialize(name)
    @name = name
  end
end

user = User.new("かつ")
puts user.name
# => かつ

attr_accessor :nameを追加するだけで、user.nameから値を取り出せるようになります。

エラーの原因を切り分けるコツは、「値が入っていないのか」「値を取り出す方法がないのか」を区別することです。

NoMethodErrorは後者のケースであることが多いので、まずはattr_accessorattr_readerの付け忘れを疑ってみましょう。

まとめ

この記事のポイント

  • オブジェクト指向は、データと処理をひとまとめにして扱う考え方
  • クラスは設計図、インスタンスはそこから作られた実体
  • initializeは、newのタイミングで自動実行されるコンストラクタ
  • @が付く変数はインスタンス変数で、そのままでは外部から読み書きできない
  • attr_accessorを使うと、getter・setterを自動生成できる

次に読むべき記事

クラスの基本を理解したら、次は複数のクラスで処理を共有できる「モジュールとMix-in」の使い方に進むのがおすすめです。

タグ: Ruby, 初心者向け, 基本文法

タイトルとURLをコピーしました