22
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

オブジェクト指向×Ruby入門:継承とポリモーフィズム(多態性[たたいせい ])をコードで体感しよう

Last updated at Posted at 2025-03-31

はじめに

オブジェクト指向プログラミング(OOP)は、再利用性が高く、保守しやすいコードを書くための考え方です。Rubyはオブジェクト指向が自然に書ける言語であり、OOPの基本を学ぶにはとても適しています。

本記事では、OOPの中でも特に重要な2つの概念に焦点を当てます。

  • 継承(Inheritance)
    → 共通の機能を親クラスにまとめ、子クラスで再利用する仕組み。

  • ポリモーフィズム(Polymorphism / 多態性)
    → 「同じメソッド名」でも、呼び出すオブジェクトによって異なる振る舞いができる仕組み。

この2つを理解することで、現実世界のモデルを柔軟にコードに落とし込む力が身につきます。

Rubyで「車」「デバイス」「社員」「動物」を例に、これらの概念をコードで体感しながら学んでいきましょう。

[ 例1 ] 車(Car)

1. 抽象クラスとは?(Templateの役割)

まずは「Car」という抽象的なクラスを定義します。共通のインターフェースや基本的な振る舞いを定義するけれど、直接インスタンス化はされないクラスです。

class Car
  attr_reader :brand, :model

  def initialize(brand, model)
    @brand = brand
    @model = model
  end

  def start_engine
    puts "#{brand} #{model}: エンジンを始動します"
  end

  def drive
    raise NotImplementedError, "このメソッドはサブクラスで実装してください"
  end

  def stop_engine
    puts "#{brand} #{model}: エンジンを停止します"
  end
end

brandとmodelは各車種に共通の属性。
driveは抽象メソッド。サブクラスに実装を強制します。

2. サブクラスで具体的な車種を定義する

抽象クラスCarを継承して、各車種ごとの挙動を具体的に実装します。

class SportsCar < Car
  def drive
    puts "#{brand} #{model}: スポーツカーが高速で走行します"
  end

  def turbo_boost
    puts "#{brand} #{model}: ターボブーストを使用!加速中!"
  end
end

class Truck < Car
  def drive
    puts "#{brand} #{model}: トラックが荷物を積んでゆっくり走ります"
  end

  def load_cargo(weight)
    puts "#{brand} #{model}: #{weight}kg の荷物を積載しました"
  end
end

class ElectricCar < Car
  def drive
    puts "#{brand} #{model}: 電気自動車が静かに走行します"
  end

  def charge_battery
    puts "#{brand} #{model}: バッテリーを充電中です"
  end
end

class SUV < Car
  def drive
    puts "#{brand} #{model}: SUVがオフロードを力強く走行します"
  end

  def switch_4wd(on)
    if on
      puts "#{brand} #{model}: 4WDモードをONにしました"
    else
      puts "#{brand} #{model}: 4WDモードをOFFにしました"
    end
  end
end

class HybridCar < Car
  def drive
    puts "#{brand} #{model}: ハイブリッドカーが省エネ走行します"
  end

  def switch_mode(mode)
    puts "#{brand} #{model}: #{mode}モードに切り替えました"
  end
end

3. 動作確認してみよう

cars = [
  SportsCar.new("Ferrari", "488"),
  Truck.new("Isuzu", "Giga"),
  ElectricCar.new("Tesla", "Model S"),
  SUV.new("Toyota", "Land Cruiser"),
  HybridCar.new("Toyota", "Prius")
]

cars.each do |car|
  car.start_engine
  car.drive
  car.stop_engine
  puts "---"
end

# サブクラス固有のメソッドを試す
puts "追加機能のテスト"
car = SportsCar.new("Porsche", "911")
car.turbo_boost

truck = Truck.new("Hino", "Profia")
truck.load_cargo(5000)

ev = ElectricCar.new("Nissan", "Leaf")
ev.charge_battery

suv = SUV.new("Jeep", "Wrangler")
suv.switch_4wd(true)

hybrid = HybridCar.new("Honda", "Insight")
hybrid.switch_mode("EV")
Ferrari 488: エンジンを始動します
Ferrari 488: スポーツカーが高速で走行します
Ferrari 488: エンジンを停止します
---
Isuzu Giga: エンジンを始動します
Isuzu Giga: トラックが荷物を積んでゆっくり走ります
Isuzu Giga: エンジンを停止します
---
Tesla Model S: エンジンを始動します
Tesla Model S: 電気自動車が静かに走行します
Tesla Model S: エンジンを停止します
---
Toyota Land Cruiser: エンジンを始動します
Toyota Land Cruiser: SUVがオフロードを力強く走行します
Toyota Land Cruiser: エンジンを停止します
---
Toyota Prius: エンジンを始動します
Toyota Prius: ハイブリッドカーが省エネ走行します
Toyota Prius: エンジンを停止します
---
追加機能のテスト
Porsche 911: ターボブーストを使用!加速中!
Hino Profia: 5000kg の荷物を積載しました
Nissan Leaf: バッテリーを充電中です
Jeep Wrangler: 4WDモードをONにしました
Honda Insight: EVモードに切り替えました

4. 拡張性を意識した設計のメリット

  • 共通処理は親クラスに集約 → メンテナンスしやすい
  • 個別の処理はサブクラスで分岐 → 新しい車種の追加が容易
  • ポリモーフィズム(多態性) → 同じメソッドで異なる動作が可能
# 新しい車種を追加する場合
class AutonomousCar < Car
  def drive
    puts "#{brand} #{model}: 自動運転モードで走行します"
  end

  def activate_auto_pilot
    puts "#{brand} #{model}: 自動運転を開始しました"
  end
end

このように、共通のインターフェース(start_engine, drive, stop_engine)を守りながら、車種ごとに振る舞いを簡単に変えることができます。

まとめ

  • 抽象クラスは「共通の土台」としての役割
  • 抽象クラスで共通処理とインターフェースを定義
  • サブクラスは「具体的な実装」を担う
  • NotImplementedErrorで実装の強制ができる
  • 継承によりコードの再利用と拡張性が高まる。新しい車種の追加がしやすい
  • サブクラスに固有のメソッドを持たせることで、個別の機能も表現可能
  • ポリモーフィズム(多態性)によって処理を統一できる

オブジェクト指向の設計において、こうした「親クラス + サブクラス」の構造を理解することは、現実の問題を柔軟にモデリングするうえでとても有用です。

全体のコードはこちら

class Car
  attr_reader :brand, :model

  def initialize(brand, model)
    @brand = brand
    @model = model
  end

  def start_engine
    puts "#{brand} #{model}: エンジンを始動します"
  end

  def drive
    raise NotImplementedError, "このメソッドはサブクラスで実装してください"
  end

  def stop_engine
    puts "#{brand} #{model}: エンジンを停止します"
  end
end

class SportsCar < Car
  def drive
    puts "#{brand} #{model}: スポーツカーが高速で走行します"
  end

  def turbo_boost
    puts "#{brand} #{model}: ターボブーストを使用!加速中!"
  end
end

class Truck < Car
  def drive
    puts "#{brand} #{model}: トラックが荷物を積んでゆっくり走ります"
  end

  def load_cargo(weight)
    puts "#{brand} #{model}: #{weight}kg の荷物を積載しました"
  end
end

class ElectricCar < Car
  def drive
    puts "#{brand} #{model}: 電気自動車が静かに走行します"
  end

  def charge_battery
    puts "#{brand} #{model}: バッテリーを充電中です"
  end
end

class SUV < Car
  def drive
    puts "#{brand} #{model}: SUVがオフロードを力強く走行します"
  end

  def switch_4wd(on)
    if on
      puts "#{brand} #{model}: 4WDモードをONにしました"
    else
      puts "#{brand} #{model}: 4WDモードをOFFにしました"
    end
  end
end

class HybridCar < Car
  def drive
    puts "#{brand} #{model}: ハイブリッドカーが省エネ走行します"
  end

  def switch_mode(mode)
    puts "#{brand} #{model}: #{mode}モードに切り替えました"
  end
end

cars = [
  SportsCar.new("Ferrari", "488"),
  Truck.new("Isuzu", "Giga"),
  ElectricCar.new("Tesla", "Model S"),
  SUV.new("Toyota", "Land Cruiser"),
  HybridCar.new("Toyota", "Prius")
]

cars.each do |car|
  car.start_engine
  car.drive
  car.stop_engine
  puts "---"
end

# サブクラス固有のメソッドを試す
puts "追加機能のテスト"
car = SportsCar.new("Porsche", "911")
car.turbo_boost

truck = Truck.new("Hino", "Profia")
truck.load_cargo(5000)

ev = ElectricCar.new("Nissan", "Leaf")
ev.charge_battery

suv = SUV.new("Jeep", "Wrangler")
suv.switch_4wd(true)

hybrid = HybridCar.new("Honda", "Insight")
hybrid.switch_mode("EV")

以下イメージが湧きづらい人(自分がそのタイプ)のために別の例を挙げて脳に刷り込みます。

[ 例2 ] 電子機器 (Device)

1. 抽象クラスの定義(共通インターフェース)

全てのデバイスに共通する処理やインターフェースを抽象クラスとしてまとめます。

class Device
  attr_reader :brand, :model

  def initialize(brand, model)
    @brand = brand
    @model = model
  end

  def power_on
    puts "#{brand} #{model}: 電源を入れました"
  end

  def operate
    raise NotImplementedError, "このメソッドはサブクラスで実装してください"
  end

  def power_off
    puts "#{brand} #{model}: 電源を切りました"
  end
end

2. サブクラスで具体的なデバイスを定義する

class Smartphone < Device
  def operate
    puts "#{brand} #{model}: スマホでアプリを起動しています"
  end

  def take_photo
    puts "#{brand} #{model}: カメラで写真を撮影しました"
  end
end

class Laptop < Device
  def operate
    puts "#{brand} #{model}: ノートPCでプログラムを実行中です"
  end

  def compile_code
    puts "#{brand} #{model}: コードをコンパイルしました"
  end
end

class Tablet < Device
  def operate
    puts "#{brand} #{model}: タブレットで電子書籍を読んでいます"
  end

  def draw_sketch
    puts "#{brand} #{model}: タッチペンでスケッチしています"
  end
end

class SmartWatch < Device
  def operate
    puts "#{brand} #{model}: スマートウォッチで健康データを記録中です"
  end

  def measure_heart_rate
    puts "#{brand} #{model}: 心拍数を測定しました"
  end
end

class GameConsole < Device
  def operate
    puts "#{brand} #{model}: ゲームをプレイしています"
  end

  def insert_game(title)
    puts "#{brand} #{model}: ゲーム『#{title}』を挿入しました"
  end
end

3. 動作確認してみよう

devices = [
  Smartphone.new("Apple", "iPhone 14"),
  Laptop.new("Dell", "XPS 13"),
  Tablet.new("Samsung", "Galaxy Tab"),
  SmartWatch.new("Fitbit", "Versa 3"),
  GameConsole.new("Sony", "PlayStation 5")
]

devices.each do |device|
  device.power_on
  device.operate
  device.power_off
  puts "---"
end

# サブクラス固有のメソッドを使ってみる
puts "追加機能のテスト"

phone = Smartphone.new("Google", "Pixel 7")
phone.take_photo

laptop = Laptop.new("Apple", "MacBook Pro")
laptop.compile_code

tablet = Tablet.new("Microsoft", "Surface Pro")
tablet.draw_sketch

watch = SmartWatch.new("Garmin", "Venu 2")
watch.measure_heart_rate

game = GameConsole.new("Nintendo", "Switch")
game.insert_game("ゼルダの伝説")
Apple iPhone 14: 電源を入れました
Apple iPhone 14: スマホでアプリを起動しています
Apple iPhone 14: 電源を切りました
---
Dell XPS 13: 電源を入れました
Dell XPS 13: ノートPCでプログラムを実行中です
Dell XPS 13: 電源を切りました
---
Samsung Galaxy Tab: 電源を入れました
Samsung Galaxy Tab: タブレットで電子書籍を読んでいます
Samsung Galaxy Tab: 電源を切りました
---
Fitbit Versa 3: 電源を入れました
Fitbit Versa 3: スマートウォッチで健康データを記録中です
Fitbit Versa 3: 電源を切りました
---
Sony PlayStation 5: 電源を入れました
Sony PlayStation 5: ゲームをプレイしています
Sony PlayStation 5: 電源を切りました
---
追加機能のテスト
Google Pixel 7: カメラで写真を撮影しました
Apple MacBook Pro: コードをコンパイルしました
Microsoft Surface Pro: タッチペンでスケッチしています
Garmin Venu 2: 心拍数を測定しました
Nintendo Switch: ゲーム『ゼルダの伝説』を挿入しました

全てのコードはこちら

class Device
  attr_reader :brand, :model

  def initialize(brand, model)
    @brand = brand
    @model = model
  end

  def power_on
    puts "#{brand} #{model}: 電源を入れました"
  end

  def operate
    raise NotImplementedError, "このメソッドはサブクラスで実装してください"
  end

  def power_off
    puts "#{brand} #{model}: 電源を切りました"
  end
end

class Smartphone < Device
  def operate
    puts "#{brand} #{model}: スマホでアプリを起動しています"
  end

  def take_photo
    puts "#{brand} #{model}: カメラで写真を撮影しました"
  end
end

class Laptop < Device
  def operate
    puts "#{brand} #{model}: ノートPCでプログラムを実行中です"
  end

  def compile_code
    puts "#{brand} #{model}: コードをコンパイルしました"
  end
end

class Tablet < Device
  def operate
    puts "#{brand} #{model}: タブレットで電子書籍を読んでいます"
  end

  def draw_sketch
    puts "#{brand} #{model}: タッチペンでスケッチしています"
  end
end

class SmartWatch < Device
  def operate
    puts "#{brand} #{model}: スマートウォッチで健康データを記録中です"
  end

  def measure_heart_rate
    puts "#{brand} #{model}: 心拍数を測定しました"
  end
end

class GameConsole < Device
  def operate
    puts "#{brand} #{model}: ゲームをプレイしています"
  end

  def insert_game(title)
    puts "#{brand} #{model}: ゲーム『#{title}』を挿入しました"
  end
end

devices = [
  Smartphone.new("Apple", "iPhone 14"),
  Laptop.new("Dell", "XPS 13"),
  Tablet.new("Samsung", "Galaxy Tab"),
  SmartWatch.new("Fitbit", "Versa 3"),
  GameConsole.new("Sony", "PlayStation 5")
]

devices.each do |device|
  device.power_on
  device.operate
  device.power_off
  puts "---"
end

# サブクラス固有のメソッドを使ってみる
puts "追加機能のテスト"

phone = Smartphone.new("Google", "Pixel 7")
phone.take_photo

laptop = Laptop.new("Apple", "MacBook Pro")
laptop.compile_code

tablet = Tablet.new("Microsoft", "Surface Pro")
tablet.draw_sketch

watch = SmartWatch.new("Garmin", "Venu 2")
watch.measure_heart_rate

game = GameConsole.new("Nintendo", "Switch")
game.insert_game("ゼルダの伝説")

4. 拡張性を意識した設計のメリット

  • 共通処理の集約:power_on, power_offは共通の振る舞いとして抽象クラスに持たせる
  • 個別の処理の分離:operateや特有の操作は各デバイスに実装
  • 柔軟な拡張:新しいデバイスの追加が容易
class EReader < Device
  def operate
    puts "#{brand} #{model}: 電子書籍を読んでいます"
  end

  def highlight_text
    puts "#{brand} #{model}: テキストにハイライトを付けました"
  end
end

[ 例3 ] 社員(Employee)

1. 抽象クラスの定義(共通の職種基盤)

すべての社員に共通する処理を親クラスとしてまとめます。

class Employee
  attr_reader :name, :department

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

  def clock_in
    puts "#{name}#{department}部門): 出勤しました"
  end

  def work
    raise NotImplementedError, "このメソッドはサブクラスで実装してください"
  end

  def clock_out
    puts "#{name}#{department}部門): 退勤しました"
  end
end

2. サブクラスで職種ごとの役割を定義する

class Engineer < Employee
  def work
    puts "#{name}: コードを書いて機能を実装しています"
  end

  def deploy
    puts "#{name}: プロダクション環境にデプロイしました"
  end
end

class Designer < Employee
  def work
    puts "#{name}: UI/UXデザインを作成しています"
  end

  def create_mockup
    puts "#{name}: モックアップを作成しました"
  end
end

class Manager < Employee
  def work
    puts "#{name}: チームの進捗を管理しています"
  end

  def hold_meeting
    puts "#{name}: 会議を開催しました"
  end
end

class Sales < Employee
  def work
    puts "#{name}: 顧客に商品を提案しています"
  end

  def make_deal(amount)
    puts "#{name}: #{amount}万円の契約を成立させました"
  end
end

class Intern < Employee
  def work
    puts "#{name}: 研修資料を読んで勉強しています"
  end

  def report
    puts "#{name}: 1日の学びをレポートにまとめました"
  end
end

3. 動作確認してみよう

employees = [
  Engineer.new("佐藤", "開発"),
  Designer.new("田中", "デザイン"),
  Manager.new("鈴木", "マネジメント"),
  Sales.new("高橋", "営業"),
  Intern.new("山本", "人事")
]

employees.each do |employee|
  employee.clock_in
  employee.work
  employee.clock_out
  puts "---"
end

# サブクラス固有のメソッドを試す
puts "追加機能のテスト"

engineer = Engineer.new("後藤", "開発")
engineer.deploy

designer = Designer.new("斎藤", "デザイン")
designer.create_mockup

manager = Manager.new("中村", "経営")
manager.hold_meeting

sales = Sales.new("松本", "営業")
sales.make_deal(150)

intern = Intern.new("小林", "総務")
intern.report
佐藤(開発部門): 出勤しました
佐藤: コードを書いて機能を実装しています
佐藤(開発部門): 退勤しました
---
田中(デザイン部門): 出勤しました
田中: UI/UXデザインを作成しています
田中(デザイン部門): 退勤しました
---
鈴木(マネジメント部門): 出勤しました
鈴木: チームの進捗を管理しています
鈴木(マネジメント部門): 退勤しました
---
高橋(営業部門): 出勤しました
高橋: 顧客に商品を提案しています
高橋(営業部門): 退勤しました
---
山本(人事部門): 出勤しました
山本: 研修資料を読んで勉強しています
山本(人事部門): 退勤しました
---
追加機能のテスト
後藤: プロダクション環境にデプロイしました
斎藤: モックアップを作成しました
中村: 会議を開催しました
松本: 150万円の契約を成立させました
小林: 1日の学びをレポートにまとめました

全てのコード

class Employee
  attr_reader :name, :department

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

  def clock_in
    puts "#{name}#{department}部門): 出勤しました"
  end

  def work
    raise NotImplementedError, "このメソッドはサブクラスで実装してください"
  end

  def clock_out
    puts "#{name}#{department}部門): 退勤しました"
  end
end

class Engineer < Employee
  def work
    puts "#{name}: コードを書いて機能を実装しています"
  end

  def deploy
    puts "#{name}: プロダクション環境にデプロイしました"
  end
end

class Designer < Employee
  def work
    puts "#{name}: UI/UXデザインを作成しています"
  end

  def create_mockup
    puts "#{name}: モックアップを作成しました"
  end
end

class Manager < Employee
  def work
    puts "#{name}: チームの進捗を管理しています"
  end

  def hold_meeting
    puts "#{name}: 会議を開催しました"
  end
end

class Sales < Employee
  def work
    puts "#{name}: 顧客に商品を提案しています"
  end

  def make_deal(amount)
    puts "#{name}: #{amount}万円の契約を成立させました"
  end
end

class Intern < Employee
  def work
    puts "#{name}: 研修資料を読んで勉強しています"
  end

  def report
    puts "#{name}: 1日の学びをレポートにまとめました"
  end
end

employees = [
  Engineer.new("佐藤", "開発"),
  Designer.new("田中", "デザイン"),
  Manager.new("鈴木", "マネジメント"),
  Sales.new("高橋", "営業"),
  Intern.new("山本", "人事")
]

employees.each do |employee|
  employee.clock_in
  employee.work
  employee.clock_out
  puts "---"
end

# サブクラス固有のメソッドを試す
puts "追加機能のテスト"

engineer = Engineer.new("後藤", "開発")
engineer.deploy

designer = Designer.new("斎藤", "デザイン")
designer.create_mockup

manager = Manager.new("中村", "経営")
manager.hold_meeting

sales = Sales.new("松本", "営業")
sales.make_deal(150)

intern = Intern.new("小林", "総務")
intern.report

4. 拡張性を意識した設計のメリット

  • 共通処理(出退勤)は親クラスに集約
  • 個別の業務は職種ごとにカスタマイズ可能
  • 新しい役割の追加も容易に実装可能
class QAEngineer < Employee
  def work
    puts "#{name}: テストケースを実行してバグを検出しています"
  end

  def write_bug_report
    puts "#{name}: バグレポートを提出しました"
  end
end

[ 例4 ] 動物(Animal)

1. 抽象クラスの定義(動物としての共通性)

すべての動物に共通する特性や振る舞いを抽象クラスAnimalとして定義します。

class Animal
  attr_reader :name, :species

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

  def introduce
    puts "私は#{species}#{name}です"
  end

  def speak
    raise NotImplementedError, "このメソッドはサブクラスで実装してください"
  end

  def move
    raise NotImplementedError, "このメソッドはサブクラスで実装してください"
  end
end

2. サブクラスで具体的な動物を定義する

class Dog < Animal
  def speak
    puts "#{name}: ワンワン!"
  end

  def move
    puts "#{name}: 元気に走り回っています"
  end

  def fetch
    puts "#{name}: ボールを取ってきました!"
  end
end

class Cat < Animal
  def speak
    puts "#{name}: ニャー"
  end

  def move
    puts "#{name}: 静かに歩いています"
  end

  def scratch
    puts "#{name}: 爪をといでいます"
  end
end

class Bird < Animal
  def speak
    puts "#{name}: チュンチュン"
  end

  def move
    puts "#{name}: 空を飛んでいます"
  end

  def build_nest
    puts "#{name}: 巣を作っています"
  end
end

class Fish < Animal
  def speak
    puts "#{name}: ...(音を出しません)"
  end

  def move
    puts "#{name}: 水中を泳いでいます"
  end

  def lay_eggs
    puts "#{name}: 卵を産みました"
  end
end

class Horse < Animal
  def speak
    puts "#{name}: ヒヒーン"
  end

  def move
    puts "#{name}: 駆けています"
  end

  def jump
    puts "#{name}: 障害を飛び越えました"
  end
end

3. 動作確認してみよう

animals = [
  Dog.new("ポチ", "犬"),
  Cat.new("ミケ", "猫"),
  Bird.new("ピヨ", "鳥"),
  Fish.new("スイスイ", "魚"),
  Horse.new("ハヤテ", "馬")
]

animals.each do |animal|
  animal.introduce
  animal.speak
  animal.move
  puts "---"
end

# サブクラス固有のメソッドを呼び出す
puts "追加機能のテスト"

dog = Dog.new("レオ", "犬")
dog.fetch

cat = Cat.new("タマ", "猫")
cat.scratch

bird = Bird.new("コトリ", "鳥")
bird.build_nest

fish = Fish.new("キンギョ", "魚")
fish.lay_eggs

horse = Horse.new("シルフ", "馬")
horse.jump
私は犬のポチです
ポチ: ワンワン!
ポチ: 元気に走り回っています
---
私は猫のミケです
ミケ: ニャー
ミケ: 静かに歩いています
---
私は鳥のピヨです
ピヨ: チュンチュン
ピヨ: 空を飛んでいます
---
私は魚のスイスイです
スイスイ: ...(音を出しません)
スイスイ: 水中を泳いでいます
---
私は馬のハヤテです
ハヤテ: ヒヒーン
ハヤテ: 駆けています
---
追加機能のテスト
レオ: ボールを取ってきました!
タマ: 爪をといでいます
コトリ: 巣を作っています
キンギョ: 卵を産みました
シルフ: 障害を飛び越えました

全てのコード

class Animal
  attr_reader :name, :species

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

  def introduce
    puts "私は#{species}#{name}です"
  end

  def speak
    raise NotImplementedError, "このメソッドはサブクラスで実装してください"
  end

  def move
    raise NotImplementedError, "このメソッドはサブクラスで実装してください"
  end
end

class Dog < Animal
  def speak
    puts "#{name}: ワンワン!"
  end

  def move
    puts "#{name}: 元気に走り回っています"
  end

  def fetch
    puts "#{name}: ボールを取ってきました!"
  end
end

class Cat < Animal
  def speak
    puts "#{name}: ニャー"
  end

  def move
    puts "#{name}: 静かに歩いています"
  end

  def scratch
    puts "#{name}: 爪をといでいます"
  end
end

class Bird < Animal
  def speak
    puts "#{name}: チュンチュン"
  end

  def move
    puts "#{name}: 空を飛んでいます"
  end

  def build_nest
    puts "#{name}: 巣を作っています"
  end
end

class Fish < Animal
  def speak
    puts "#{name}: ...(音を出しません)"
  end

  def move
    puts "#{name}: 水中を泳いでいます"
  end

  def lay_eggs
    puts "#{name}: 卵を産みました"
  end
end

class Horse < Animal
  def speak
    puts "#{name}: ヒヒーン"
  end

  def move
    puts "#{name}: 駆けています"
  end

  def jump
    puts "#{name}: 障害を飛び越えました"
  end
end

animals = [
  Dog.new("ポチ", "犬"),
  Cat.new("ミケ", "猫"),
  Bird.new("ピヨ", "鳥"),
  Fish.new("スイスイ", "魚"),
  Horse.new("ハヤテ", "馬")
]

animals.each do |animal|
  animal.introduce
  animal.speak
  animal.move
  puts "---"
end

# サブクラス固有のメソッドを呼び出す
puts "追加機能のテスト"

dog = Dog.new("レオ", "犬")
dog.fetch

cat = Cat.new("タマ", "猫")
cat.scratch

bird = Bird.new("コトリ", "鳥")
bird.build_nest

fish = Fish.new("キンギョ", "魚")
fish.lay_eggs

horse = Horse.new("シルフ", "馬")
horse.jump

###4. 拡張性を意識した設計のメリット

  • 共通メソッドは親クラスに集約 → introduce, speak, move
  • 動物ごとの特徴的な振る舞いはサブクラスで表現
  • 新しい動物種の追加も簡単
class Elephant < Animal
  def speak
    puts "#{name}: パオーン"
  end

  def move
    puts "#{name}: ゆっくりと歩いています"
  end

  def spray_water
    puts "#{name}: 鼻で水を噴き出しました"
  end
end

まとめ (あえて脳に刷り込むため)

  • 抽象クラスは基本構造とインターフェースを提供
  • サブクラスで多様な動物の特性を実装
  • ポリモーフィズムにより共通メソッドで統一的に操作可能

番外編 共通スキルをモジュールとして定義

一部の動物が持つ共通スキル(飛ぶ・泳ぐ)をモジュールに分離して、必要なクラスでincludeします。

module Flyable
  def fly
    puts "#{name}: 空を飛んでいます"
  end
end

module Swimmable
  def swim
    puts "#{name}: 水中を泳いでいます"
  end
end

全てのコード

module Flyable
  def fly
    puts "#{name}: 空を飛んでいます"
  end
end

module Swimmable
  def swim
    puts "#{name}: 水中を泳いでいます"
  end
end

class Animal
  attr_reader :name, :species

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

  def introduce
    puts "私は#{species}#{name}です"
  end

  def speak
    raise NotImplementedError, "このメソッドはサブクラスで実装してください"
  end

  def move
    raise NotImplementedError, "このメソッドはサブクラスで実装してください"
  end
end

class Dog < Animal
  def speak
    puts "#{name}: ワンワン!"
  end

  def move
    puts "#{name}: 元気に走り回っています"
  end

  def fetch
    puts "#{name}: ボールを取ってきました!"
  end
end

class Cat < Animal
  def speak
    puts "#{name}: ニャー"
  end

  def move
    puts "#{name}: 静かに歩いています"
  end

  def scratch
    puts "#{name}: 爪をといでいます"
  end
end

class Bird < Animal
  include Flyable

  def speak
    puts "#{name}: チュンチュン"
  end

  def move
    fly
  end

  def build_nest
    puts "#{name}: 巣を作っています"
  end
end

class Fish < Animal
  include Swimmable

  def speak
    puts "#{name}: ...(音を出しません)"
  end

  def move
    swim
  end

  def lay_eggs
    puts "#{name}: 卵を産みました"
  end
end

class Horse < Animal
  def speak
    puts "#{name}: ヒヒーン"
  end

  def move
    puts "#{name}: 駆けています"
  end

  def jump
    puts "#{name}: 障害を飛び越えました"
  end
end

class Elephant < Animal
  def speak
    puts "#{name}: パオーン"
  end

  def move
    puts "#{name}: ゆっくりと歩いています"
  end

  def spray_water
    puts "#{name}: 鼻で水を噴き出しました"
  end
end

class Duck < Animal
  include Swimmable
  include Flyable

  def speak
    puts "#{name}: ガーガー"
  end

  def move
    swim
    fly
  end
end

animals = [
  Dog.new("ポチ", "犬"),
  Cat.new("ミケ", "猫"),
  Bird.new("ピヨ", "鳥"),
  Fish.new("スイスイ", "魚"),
  Horse.new("ハヤテ", "馬"),
  Elephant.new("ゾウタ", "象"),
  Duck.new("アヒルン", "アヒル")
]

animals.each do |animal|
  animal.introduce
  animal.speak
  animal.move
  puts "---"
end

# サブクラス固有のメソッドを呼び出す
puts "追加機能のテスト"

dog = Dog.new("レオ", "犬")
dog.fetch

cat = Cat.new("タマ", "猫")
cat.scratch

bird = Bird.new("コトリ", "鳥")
bird.move
bird.build_nest

fish = Fish.new("キンギョ", "魚")
fish.move
fish.lay_eggs

horse = Horse.new("シルフ", "馬")
horse.jump

elephant = Elephant.new("ダイゾウ", "象")
elephant.spray_water

duck = Duck.new("ガーコ", "アヒル")
duck.move

終わりに

今回は、Rubyを使ってオブジェクト指向設計の基本である「継承」と「ポリモーフィズム(多態性)」について解説しました。
共通の処理を親クラスにまとめたり、同じメソッド名でもクラスごとに異なる振る舞いをさせたりすることで、拡張性や再利用性の高いコードが書けるようになります。

こうした設計の考え方は、実際の開発現場でも非常に重要です。

また、株式会社シンシアでは、実務未経験のエンジニアの方や学生エンジニアインターンを採用し一緒に働いています。
※ シンシアにおける働き方の様子はこちら

弊社には年間100人程度の実務未経験の方に応募いただき、技術面接を実施しております。
この記事が少しでも学びになったという方は、ぜひ wantedly のストーリーもご覧いただけるととても嬉しいです!

22
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?