はじめに
オブジェクト指向プログラミング(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 のストーリーもご覧いただけるととても嬉しいです!