こんにちは、かつコーチです。
Rubyの学習を進めていくと、必ず「クラス」という言葉にぶつかります。
「なんとなくコピペで使えるけど、実は仕組みがよく分かっていない」という方も多いのではないでしょうか。
この記事では、Rubyにおけるクラスとオブジェクト指向の基本を、初心者の方向けに丁寧に解説します。
読み終える頃には、classを使った定義からinitialize、attr_accessorまで、自分の手で書けるようになります。
クラスとオブジェクト指向とは?
オブジェクト指向とは何か
オブジェクト指向とは、データとそれを操作する処理を「モノ(オブジェクト)」としてひとまとめにして扱う考え方です。
たとえば「ユーザー」を扱うプログラムなら、名前や年齢といったデータと、あいさつをするといった処理を、1つの「ユーザーオブジェクト」としてまとめられます。
Rubyは「すべてがオブジェクトである」という思想を徹底している言語です。
数値や文字列、配列すら、すべてオブジェクトとして扱われます。
puts 1.class # => Integer
puts "hello".class # => String
puts [1, 2].class # => Array
このように、どんな値にも.classメソッドで確認できる「型」があり、それぞれがオブジェクトとして振る舞います。
クラスとインスタンスの関係
クラスとは、オブジェクトの「設計図」です。
その設計図から実際に作られたモノをインスタンスと呼びます。
「たい焼きの型(クラス)」と「実際に焼き上がったたい焼き(インスタンス)」の関係をイメージすると分かりやすいです。
同じ型からでも、あんこ入り・チョコ入りなど、中身が異なるたい焼きを作れますよね。
クラスも同様に、同じ設計図から、名前や年齢が異なる複数のインスタンスを作成できます。
基本の書き方:クラス定義の手順
手順1:classでクラスを定義する
Rubyではclassキーワードを使ってクラスを定義します。
クラス名は大文字始まりのキャメルケース(例:User、ShoppingCart)にするのがルールです。
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に"かつ"、ageに30が渡されます。
手順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行書くだけで、name・name=・age・age=の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_accessorやattr_readerの付け忘れを疑ってみましょう。
まとめ
この記事のポイント
- オブジェクト指向は、データと処理をひとまとめにして扱う考え方
- クラスは設計図、インスタンスはそこから作られた実体
initializeは、newのタイミングで自動実行されるコンストラクタ@が付く変数はインスタンス変数で、そのままでは外部から読み書きできないattr_accessorを使うと、getter・setterを自動生成できる
次に読むべき記事
クラスの基本を理解したら、次は複数のクラスで処理を共有できる「モジュールとMix-in」の使い方に進むのがおすすめです。
タグ: Ruby, 初心者向け, 基本文法