第3章|今さら学ぶ「オブジェクト指向」
📚 シリーズ目次はこちら → 「今さら学ぶ」シリーズ — はじめに
🗺️ KnowledgeNoteの設計を確認 → 設計マップ
この章でわかること
- オブジェクト指向の本質は「変更に強いコードを作る」こと
- クラスとインスタンスの関係 — 「設計図と実物」
- 継承 — 共通の性質を親から受け継ぐ
- ポリモーフィズム — 同じ命令で違う動きをさせる
- カプセル化 — 触らせたくない部分を隠す
- モジュールとミックスイン — 継承とは別の「機能の共有」
- KnowledgeNoteのモデル設計で見る「オブジェクト指向の実践」
🏠 たとえ話で掴む「オブジェクト指向」
オブジェクト指向を一言でいうと、 「モノ(オブジェクト)を中心にプログラムを組み立てる考え方」 です。
これだけだとピンとこないので、 動物園 にたとえて考えてみる。
動物園には色々な動物がいる。犬、猫、鳥。それぞれ違う特徴を持っているが、共通点もある。
| 動物園のたとえ | オブジェクト指向 |
|---|---|
| 「動物」という大きなくくり | クラス (設計図) |
| 実際の「ポチ」「タマ」「ピー太」 | インスタンス (実物) |
| 「動物はみんな名前と年齢を持つ」 | 属性(プロパティ) |
| 「動物はみんな鳴ける」 | メソッド |
| 「犬は動物の一種」 | 継承 |
| 犬は「ワン!」、猫は「ニャー!」と鳴く | ポリモーフィズム |
| 動物の内臓の仕組みは外から触れない | カプセル化 |
オブジェクト指向を学ぶと「きれいなコードが書ける」とよく言われるが、本当の価値は 「変更に強いコードが書ける」 ことです。
新しい動物(機能)を追加するとき、既存の動物(コード)を書き直さなくて済む。そういう設計を可能にするのがオブジェクト指向の力です。
🏗️ クラスとインスタンス — 設計図と実物
クラスは「設計図」
クラス は、オブジェクトの設計図。「ユーザーとはこういうものだ」という定義を書きます。
# 設計図(クラス)を定義する
class User
# initializeは「実物を作るときに最初に実行される処理」
def initialize(name, email)
@name = name # @がつくとインスタンス変数(この実物だけのデータ)
@email = email
end
def profile
"#{@name}(#{@email})"
end
end
インスタンスは「実物」
設計図(クラス)から作った実物が インスタンス。new で作ります。
# 設計図から実物を作る
user1 = User.new("田中", "tanaka@example.com")
user2 = User.new("鈴木", "suzuki@example.com")
puts user1.profile # => "田中(tanaka@example.com)"
puts user2.profile # => "鈴木(suzuki@example.com)"
同じ設計図(Userクラス)から作っても、user1 と user2 はそれぞれ別のデータを持つ別の実物。同じ設計図から「赤い車」と「青い車」という別の実物が作れるのと同じ構造です。
インスタンス変数とattr_accessor
@name のように @ から始まる変数は インスタンス変数。そのインスタンスだけが持つデータで、クラスの外から直接アクセスはできない。
外からアクセスさせたい場合は、 attr_accessor (アトリビュート・アクセサ)を使います。
class User
attr_accessor :name, :email # 読み書き両方OK
# attr_reader :name # 読み取りだけ(書き換え不可)
# attr_writer :name # 書き込みだけ(外から読めない)
def initialize(name, email)
@name = name
@email = email
end
end
user = User.new("田中", "tanaka@example.com")
puts user.name # => "田中"(読み取り)
user.name = "山田" # 書き換え
puts user.name # => "山田"
💡 Railsの モデル (
UserやArticle)では、attr_accessorを書かなくてもDBのカラム名で自動的にアクセスできる。これはActiveRecordが裏側で自動生成してくれているからです(→ 第15章で詳しく扱います)。
🧬 継承 — 共通の性質を親から受け継ぐ
継承とは
継承 は、あるクラスの機能を別のクラスが引き継ぐ仕組み。動物園のたとえでいうと、「犬は動物の一種だから、動物の特徴(名前を持つ、動ける)をそのまま受け継いでいる」ということです。
# 親クラス(スーパークラス)
class Animal
def initialize(name)
@name = name
end
def introduce
"私は#{@name}です"
end
end
# 子クラス(サブクラス)— Animalを継承
class Dog < Animal # < で「〜を継承する」という意味
def speak
"ワン!"
end
end
class Cat < Animal
def speak
"ニャー!"
end
end
dog = Dog.new("ポチ")
puts dog.introduce # => "私はポチです"(親のメソッドが使える)
puts dog.speak # => "ワン!"(自分のメソッドも使える)
Dog クラスは introduce メソッドを自分で定義していないが、親の Animal から受け継いでいるので使える。 共通の処理を親クラスにまとめることで、コードの重複を減らせる のが継承の利点です。
Railsでの継承
Railsを書いていると、必ず見るのがこのパターン。
class ArticlesController < ApplicationController
# ...
end
ArticlesController は ApplicationController を 継承 している。ApplicationController に定義した before_action やヘルパーメソッドが全コントローラで使えるのはこのおかげです。
同じように、モデルも ApplicationRecord を継承している。
class Article < ApplicationRecord
# ApplicationRecordを継承 → ActiveRecordの機能が全て使える
# .find, .where, .create などのメソッドは継承で手に入る
end
🎭 ポリモーフィズム — 同じ命令で違う動きをさせる
ポリモーフィズム(多態性) は、同じメソッド名で呼び出しても、オブジェクトの種類によって振る舞いが変わる仕組みです。
動物園で「鳴いて!」と言ったとき、犬は「ワン!」、猫は「ニャー!」、鳥は「ピヨ!」と鳴く。命令は同じ「鳴いて」だが、動物によって結果が違う。これがポリモーフィズムです。
class Dog < Animal
def speak
"ワン!"
end
end
class Cat < Animal
def speak
"ニャー!"
end
end
class Bird < Animal
def speak
"ピヨ!"
end
end
# 同じ speak メソッドを呼ぶだけで、動物ごとに違う結果が出る
animals = [Dog.new("ポチ"), Cat.new("タマ"), Bird.new("ピー太")]
animals.each do |animal|
puts "#{animal.introduce}:#{animal.speak}"
end
# => 私はポチです:ワン!
# => 私はタマです:ニャー!
# => 私はピー太です:ピヨ!
なぜポリモーフィズムが嬉しいのか
ポイントは、 呼び出す側のコードを変えなくていい こと。
新しい動物(たとえば Frog クラス)を追加しても、animals.each のコードは一切変更不要。Frog クラスに speak メソッドを定義するだけで、自然と全体に組み込まれます。
# 新しいクラスを追加するだけ。既存コードの変更なし!
class Frog < Animal
def speak
"ケロケロ!"
end
end
これが「変更に強い」設計。RailsではKnowledgeNoteの「いいね」機能など、 ポリモーフィック関連 として実際に使われています(→ 第18章で詳しく扱います)。
🔒 カプセル化 — ATMの裏側は触らせない
カプセル化 は、オブジェクトの内部の仕組みを外から隠すこと。
ATMで考えるとわかりやすい。利用者がやることは「カードを入れる」「暗証番号を入力する」「金額を指定する」だけ。ATMの中で紙幣をどうやって数えているか、通信をどうしているかは知らなくていいし、触れない。
プログラムでも同じ。 外から使う人が知る必要のない処理は隠す ことで、間違った使い方を防ぎます。
class BankAccount
def initialize(owner, balance)
@owner = owner
@balance = balance # 残高は外から直接いじれないようにする
end
# 外から使えるメソッド(公開インターフェース)
def deposit(amount)
if amount > 0
@balance += amount
"#{amount}円を入金しました。残高:#{@balance}円"
else
"入金額が不正です"
end
end
def balance_info
"#{@owner}さんの残高:#{@balance}円"
end
private # ここから下は外から呼べない
# 内部でだけ使うメソッド(利用者には見せない)
def calculate_interest
@balance * 0.001 # 利息計算(内部処理)
end
end
account = BankAccount.new("田中", 10000)
puts account.deposit(5000) # => "5000円を入金しました。残高:15000円"
puts account.balance_info # => "田中さんの残高:15000円"
# account.calculate_interest # => エラー!privateメソッドは外から呼べない
Rubyのアクセス制御
Rubyでは、メソッドの公開範囲を3段階で制御できる。
| キーワード | 意味 | ATMのたとえ |
|---|---|---|
public |
どこからでも呼べる(デフォルト) | ATMのボタン。利用者が押せる |
private |
そのクラスの中からだけ呼べる | ATMの内部基板。利用者は触れない |
protected |
そのクラスと子クラスから呼べる | ATMのメンテナンス用パネル。係員だけ触れる |
💡
privateとprotectedの使い分けは第4章で詳しく扱います。まずは「private= 外から隠す」が押さえられていれば大丈夫です。
🧩 モジュールとミックスイン — 継承とは別の「機能の共有」
なぜ継承だけでは足りないか
Rubyの継承は 単一継承。つまり、1つのクラスが持てる親クラスは1つだけ。「犬は動物でもありペットでもある」のように、複数の分類にまたがる機能を共有したい場合、継承だけでは対応できません。
ここで登場するのが モジュール(Module) です。
モジュールとは — 技術的な定義
モジュール は、メソッドや定数をまとめた入れ物。クラスとの最大の違いは、 インスタンスを作れない こと。モジュールは設計図ではなく、 機能のパーツ集 のようなもの。
クラスにモジュールを include すると、そのモジュールのメソッドがクラスに追加される。これを ミックスイン と呼びます。
# モジュール = 機能のパーツ集
module Taggable
def add_tag(tag)
@tags ||= []
@tags << tag
end
def tags
@tags || []
end
end
# 記事にもユーザーにも「タグ機能」をつけたい
class Article < ApplicationRecord
include Taggable # ミックスイン
end
class User < ApplicationRecord
include Taggable # 同じモジュールを別のクラスにも
end
第2章で学んだ Enumerable も実はモジュール。ArrayやHashが include Enumerable しているから、select や map が使えていたということです。
継承 vs ミックスインの使い分け
| 方法 | 使う場面 | Railsでの例 |
|---|---|---|
継承(<) |
「AはBの一種」という関係 | Article < ApplicationRecord |
ミックスイン(include) |
「AにもBにも同じ機能を持たせたい」 |
include Enumerable、include Taggable
|
🛠️ KnowledgeNoteでの具体例
KnowledgeNoteのモデルで、ここまでの概念が実際にどう使われるか。
# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
# 全モデル共通の親クラス(継承の起点)
self.abstract_class = true
end
# app/models/user.rb
class User < ApplicationRecord # ApplicationRecordを継承
has_many :articles, dependent: :destroy
has_many :comments, dependent: :destroy
has_many :likes, dependent: :destroy
has_secure_password # パスワード暗号化(カプセル化:内部の暗号化処理は隠蔽)
validates :name, presence: true
validates :email_address, presence: true, uniqueness: true
# 公開メソッド — 外から呼んで使う
def display_name
"@#{name}"
end
def has_role?(role_name)
roles.exists?(name: role_name.to_s)
end
private # カプセル化:外からは呼ばせない
# 内部でだけ使うヘルパーメソッド
def normalize_email
self.email_address = email_address.strip.downcase
end
end
# app/models/article.rb
class Article < ApplicationRecord
belongs_to :user
has_many :comments, dependent: :destroy
has_many :likes, as: :likeable, dependent: :destroy # ポリモーフィズム!
validates :title, presence: true
validates :body, presence: true
scope :published, -> { where(published: true) }
def author_name
user.display_name # Userクラスのメソッドを呼ぶだけ
end
end
has_many :likes, as: :likeable の部分に注目。記事(Article)にもコメント(Comment)にも「いいね」がつけられるが、呼び出し方は同じ likes。これがポリモーフィズムの実践です。設計が大きくなったときのパターン(Service Object等)については(→ 第25章で詳しく扱います)。
# ポリモーフィズムの実例 — 記事でもコメントでも同じように「いいね」できる
article = Article.find(1)
comment = Comment.find(1)
article.likes.count # => 記事のいいね数
comment.likes.count # => コメントのいいね数
# どちらも .likes で統一的にアクセスできる
📊 概念のまとめ — なぜオブジェクト指向が大事か
| 概念 | 何を解決するか | KnowledgeNoteでの例 |
|---|---|---|
| 継承 | コードの重複を減らす | 全モデルが ApplicationRecord を継承 |
| ポリモーフィズム | 新しい種類を追加しやすくする | 記事にもコメントにも同じ likes が使える |
| カプセル化 | 間違った使い方を防ぐ |
has_secure_password でパスワード処理を隠蔽 |
| ミックスイン | 継承なしで機能を共有する |
include Enumerable で配列処理メソッドを一括取得 |
共通する目的は 「変更に強いコードを作る」 こと。
機能を追加するときに既存コードを壊さない。担当が変わっても安全に使える。これがオブジェクト指向で設計する本当の理由です。
💼 面接で聞かれたら?
Q:オブジェクト指向の3つの特徴を説明してください。
「オブジェクト指向の3つの特徴は、継承・ポリモーフィズム・カプセル化です。継承は親クラスの機能を子クラスが引き継ぐ仕組みで、コードの重複を減らせます。ポリモーフィズムは同じメソッド名でもオブジェクトの種類によって振る舞いが変わる仕組みで、機能追加時に既存コードの変更が不要になります。カプセル化は内部の実装を隠して公開インターフェースだけを外に見せる仕組みで、安全に使える設計を実現します。」
深掘りされたら:
- 「Railsでの継承の例は?」→ 全コントローラが
ApplicationControllerを継承し、全モデルがApplicationRecordを継承している。共通の処理を親に書けば全体に行き渡る。- 「ポリモーフィズムの実例は?」→ 「いいね」機能。記事にもコメントにもいいねがつけられるが、Likeモデルは1つで、
likeable_typeで対象を区別する(ポリモーフィック関連)。- 「モジュールとクラスの違いは?」→ クラスはインスタンスを作れるが、モジュールは作れない。モジュールは
includeでクラスに機能を追加する(ミックスイン)ための仕組み。Rubyは単一継承なので、複数の機能を共有したいときにモジュールを使う。
🔗 もっと深く知りたい人へ(1次情報リンク)
- Ruby公式リファレンス:クラスとオブジェクト — クラス定義、継承、アクセス制御の公式解説
- Ruby公式リファレンス:Module — ミックスインやモジュールの仕組み
- Rails ガイド:Active Record の基礎 — モデルとDBの対応関係(継承の実践例)
まとめ
- ✅ オブジェクト指向の本質は「変更に強いコードを作る」こと
- ✅ クラスは「設計図」、インスタンスは「実物」。
newで設計図から実物を作る - ✅ 継承は親クラスの機能を子が受け継ぐ仕組み。Railsでは
< ApplicationRecordが代表例 - ✅ ポリモーフィズムは同じメソッド名で異なる振る舞いを実現する。機能追加時に既存コードを壊さない
- ✅ カプセル化は
privateで内部処理を隠す。安全なインターフェースだけを外に公開する - ✅ モジュールは
includeでクラスに機能を追加する(ミックスイン)。Enumerableもモジュール
📚 シリーズ目次:「今さら学ぶ」シリーズ — はじめに