Ruby/Railsで学ぶオブジェクト指向入門 オブジェクト指向って何ぞや

に公開

Ruby/Railsで学ぶオブジェクト指向プログラミング入門

はじめに

こんにちは!本記事では、Ruby/Railsを使ってオブジェクト指向プログラミングの基礎について記載しています。
オブジェクト指向プログラミングという言葉を知ってはいましたが、実際はそれってなんやねんという感じだったので、学習したことを記事化してみました。特にRubyは「すべてがオブジェクト」という思想を持つ言語であり、オブジェクト指向の考え方を学ぶのには合っているなーと感じましたーこの知識はRailsアプリケーションを開発する際にも必ず役立つでしょう。
5部構成となっており、以下の目次で進めていきます。本記事は1章目のオブジェクト指向の概要と基本概念を記載しております。2章目以降は別の記事を作成していく予定です!よろしくお願いいたします!

目次

1. オブジェクト指向の概要と基本概念

  • 1.1 オブジェクト指向とは?
  • 1.2 プログラミングパラダイムの比較(手続き型 vs オブジェクト指向)
  • 1.3 オブジェクト指向の三大要素(カプセル化、継承、ポリモーフィズム)
  • 1.4 クラスとオブジェクトの関係

2. クラスとオブジェクトの基礎

  • 2.1 クラスの定義とオブジェクトの生成
  • 2.2 フィールド(プロパティ)とメソッドの基本
  • 2.3 コンストラクタの役割
  • 2.4 実践問題と解説(2問)

3. カプセル化とアクセス修飾子

  • 3.1 カプセル化の概念
  • 3.2 private / protected / public の使い方
  • 3.3 ゲッターとセッターの活用
  • 3.4 実践問題と解説(2問)

4. 継承とクラスの拡張

  • 4.1 継承とは?(親クラス・子クラスの関係)
  • 4.2 super の使い方
  • 4.3 メソッドのオーバーライド
  • 4.4 実践問題と解説(2問)

5. インターフェースと抽象クラス

  • 5.1 ポリモーフィズムの基本概念
  • 5.2 Rubyにおけるインターフェース的な実装
  • 5.3 Rubyにおける抽象クラス
  • 5.4 実践問題と解説(2問)

1. オブジェクト指向の概要と基本概念

1.1 オブジェクト指向とは?

オブジェクト指向プログラミング(Object-Oriented Programming、略してOOP)とは、プログラムを「オブジェクト」と呼ばれる部品の集まりとして構成する考え方です。

オブジェクトとは、データ(属性)と、そのデータを操作するための処理(メソッド)をひとまとめにしたものです。例えば「車」というオブジェクトには、「色」「速度」「モデル名」などの属性と、「走る」「止まる」「曲がる」などの操作が含まれます。

車オブジェクト
  |-- データ(属性)
  |     |-- 色: 赤
  |     |-- 速度: 60km/h
  |     |-- モデル名: フィット
  |
  |-- 処理(メソッド)
        |-- 走る()
        |-- 止まる()
        |-- 曲がる()

オブジェクト指向プログラミングでは、このようなオブジェクトを組み合わせてプログラムを構築します。各オブジェクトはそれぞれ独立しており、他のオブジェクトとメッセージ(メソッド呼び出し)を通じて通信します。

Rubyでは「すべてがオブジェクト」です。数値、文字列、配列、さらにはtruefalseなど、あらゆるものがオブジェクトとして扱われます。

1.2 プログラミングパラダイムの比較(手続き型 vs オブジェクト指向)

プログラミングには様々なアプローチ(パラダイム)があります。オブジェクト指向と比較してよく挙げられるのが「手続き型プログラミング」です。

手続き型プログラミング:

  • プログラムは一連の手続き(処理)の集まりとして構成されます。
  • データとそれを操作する処理が分離しています。
  • 上から下へと順番に実行される処理の流れが中心です。

オブジェクト指向プログラミング:

  • プログラムはオブジェクトの集まりとして構成されます。
  • データ(属性)と処理(メソッド)が一つのオブジェクトにまとめられています。
  • オブジェクト間の相互作用がプログラムの動作を決定します。

簡単な例で比較してみましょう。「銀行口座」を表現する場合:

手続き型の例:
# データ
account_number = "12345"
balance = 1000
owner_name = "山田太郎"

# 処理(関数)
def deposit(balance, amount)
  return balance + amount
end

def withdraw(balance, amount)
  if balance >= amount
    return balance - amount
  else
    puts "残高不足です"
    return balance
  end
end

# 使用例
balance = deposit(balance, 500)  # 入金
puts "残高: #{balance}円"        # => 残高: 1500円

balance = withdraw(balance, 2000) # 出金(失敗)
puts "残高: #{balance}円"         # => 残高不足です
                                 # => 残高: 1500円
オブジェクト指向の例:
# BankAccountクラス(設計図)
class BankAccount
  attr_reader :account_number, :owner_name, :balance
  
  def initialize(account_number, owner_name, initial_balance = 0)
    @account_number = account_number
    @owner_name = owner_name
    @balance = initial_balance
  end
  
  def deposit(amount)
    @balance += amount
  end
  
  def withdraw(amount)
    if @balance >= amount
      @balance -= amount
      return true
    else
      puts "残高不足です"
      return false
    end
  end
end

# 使用例
account = BankAccount.new("12345", "山田太郎", 1000)  # オブジェクト生成
account.deposit(500)  # 入金
puts "残高: #{account.balance}円"  # => 残高: 1500円

account.withdraw(2000)  # 出金(失敗)
puts "残高: #{account.balance}円"  # => 残高不足です
                                  # => 残高: 1500円

オブジェクト指向では、データ(口座番号、所有者名、残高)と処理(入金、出金)が一つのBankAccountオブジェクトにカプセル化されています。これにより、コードの構造がより明確になり、実際の世界の概念(銀行口座)とプログラム内の表現が近くなります。

1.3 オブジェクト指向の三大要素(カプセル化、継承、ポリモーフィズム)

オブジェクト指向プログラミングの基本概念は主に3つあります。

1. カプセル化(Encapsulation)

カプセル化とは、データ(属性)と、それを操作するメソッド(振る舞い)を一つのオブジェクトにまとめ、外部からのアクセスを制限することです。

カプセル化の利点:

  • オブジェクトの内部データを外部から不適切に変更されることを防げる
  • オブジェクトの内部実装を変更しても、外部からの使い方を変えなくてよい
class Person
  def initialize(name, age)
    @name = name  # @から始まる変数はインスタンス変数
    @age = age    # 直接外部からアクセスできない
  end
  
  # 外部からデータにアクセスするためのメソッド
  def name
    @name
  end
  
  def age
    @age
  end
  
  # データを変更するためのメソッド(ここでデータの検証ができる)
  def age=(new_age)
    if new_age >= 0
      @age = new_age
    else
      puts "年齢には0以上の値を設定してください"
    end
  end
end

person = Person.new("鈴木一郎", 30)
puts person.name  # => 鈴木一郎
puts person.age   # => 30

person.age = -5   # => 年齢には0以上の値を設定してください
                  # 無効な値は設定されない
puts person.age   # => 30

2. 継承(Inheritance)

継承とは、既存のクラス(親クラス)の特性を引き継いで、新しいクラス(子クラス)を作る機能です。子クラスは親クラスのすべての属性とメソッドを継承し、さらに独自の機能を追加したり、既存の機能を上書き(オーバーライド)したりできます。

継承の利点:

  • コードの再利用性が高まる
  • 共通の機能を一箇所で管理できる
  • コードの階層構造が明確になる
# 親クラス
class Animal
  def initialize(name)
    @name = name
  end
  
  def speak
    puts "#{@name}が鳴きます"
  end
end

# 子クラス
class Dog < Animal  # < は継承を表す
  def speak  # メソッドのオーバーライド
    puts "#{@name}がワンと吠えます"
  end
  
  def fetch
    puts "#{@name}がボールを取ってきます"
  end
end

# 子クラス
class Cat < Animal
  def speak  # メソッドのオーバーライド
    puts "#{@name}がニャーと鳴きます"
  end
  
  def scratch
    puts "#{@name}が引っかきます"
  end
end

dog = Dog.new("ポチ")
cat = Cat.new("タマ")

dog.speak    # => ポチがワンと吠えます
dog.fetch    # => ポチがボールを取ってきます

cat.speak    # => タマがニャーと鳴きます
cat.scratch  # => タマが引っかきます

3. ポリモーフィズム(Polymorphism)

ポリモーフィズム(多態性)とは、同じインターフェース(メソッド名)で異なる実装を持つことができる機能です。これにより、異なる種類のオブジェクトを同じ方法で扱うことができます。

ポリモーフィズムの利点:

  • コードの柔軟性と拡張性が高まる
  • 新しい種類のオブジェクトを追加しやすくなる
  • より抽象的なコードが書ける
# 形状の抽象クラス
class Shape
  def area
    raise NotImplementedError, "サブクラスで実装する必要があります"
  end
end

# 具体的な形状クラス
class Circle < Shape
  def initialize(radius)
    @radius = radius
  end
  
  def area
    Math::PI * @radius ** 2
  end
end

class Rectangle < Shape
  def initialize(width, height)
    @width = width
    @height = height
  end
  
  def area
    @width * @height
  end
end

class Triangle < Shape
  def initialize(base, height)
    @base = base
    @height = height
  end
  
  def area
    @base * @height / 2.0
  end
end

# 同じメソッド名で異なる実装を呼び出す
shapes = [
  Circle.new(5),
  Rectangle.new(4, 6),
  Triangle.new(3, 8)
]

shapes.each do |shape|
  puts "面積: #{shape.area}"
end

# 出力:
# 面積: 78.53981633974483
# 面積: 24
# 面積: 12.0

これらの例では、異なる形状クラスがすべて同じareaメソッドを持っていますが、それぞれ異なる方法で面積を計算しています。外部からは、どのクラスのオブジェクトでも同じareaメソッドを呼び出すだけで適切な結果が得られます。

1.4 クラスとオブジェクトの関係

オブジェクト指向プログラミングにおいて、クラスオブジェクト(インスタンス)の関係は非常に重要です。

クラスとは?

クラスは、オブジェクトの「設計図」や「型」のようなものです。クラスには以下の要素が含まれます:

  • インスタンス変数(データ、属性)
  • メソッド(振る舞い、操作)
  • 初期化方法

オブジェクト(インスタンス)とは?

オブジェクト(インスタンス)は、クラスに基づいて作られた実体です。同じクラスから作られたオブジェクトは同じ構造と振る舞いを持ちますが、それぞれ独自のデータ(状態)を持ちます。

例えば、「車」をクラスとすると、「私の赤いフィット」「隣人の青いプリウス」などは、そのクラスのインスタンス(オブジェクト)です。

# Carクラス(設計図)
class Car
  attr_accessor :color, :model, :speed
  
  def initialize(color, model)
    @color = color
    @model = model
    @speed = 0
  end
  
  def accelerate(amount)
    @speed += amount
  end
  
  def brake(amount)
    @speed = [@speed - amount, 0].max  # 速度は最低0
  end
  
  def info
    "#{@color}#{@model}(現在速度: #{@speed}km/h)"
  end
end

# Carクラスからオブジェクトを作成
my_car = Car.new("赤", "フィット")
friends_car = Car.new("青", "プリウス")

# それぞれのオブジェクトは独自の状態を持つ
my_car.accelerate(60)
friends_car.accelerate(40)

puts my_car.info      # => 赤のフィット(現在速度: 60km/h)
puts friends_car.info # => 青のプリウス(現在速度: 40km/h)

# 同じメソッドでも、呼び出すオブジェクトによって結果が異なる
my_car.brake(20)
friends_car.brake(10)

puts my_car.info      # => 赤のフィット(現在速度: 40km/h)
puts friends_car.info # => 青のプリウス(現在速度: 30km/h)

このように、クラスはオブジェクトの「種類」を定義し、オブジェクトはそのクラスの実体として個別のデータを持ちます。プログラム内では、多くの場合、同じクラスの複数のオブジェクトを作成して使用します。

クラスメソッドとインスタンスメソッド

Rubyでは、クラス自体に関連するメソッド(クラスメソッド)と、クラスのインスタンスに関連するメソッド(インスタンスメソッド)があります。

class Temperature
  # クラスメソッド(self.をつける)
  def self.celsius_to_fahrenheit(celsius)
    (celsius * 9/5.0) + 32
  end
  
  # インスタンスメソッド
  def initialize(celsius)
    @celsius = celsius
  end
  
  def fahrenheit
    Temperature.celsius_to_fahrenheit(@celsius)
  end
end

# クラスメソッドの呼び出し(インスタンス不要)
puts Temperature.celsius_to_fahrenheit(30)  # => 86.0

# インスタンスメソッドの呼び出し(インスタンスが必要)
temp = Temperature.new(30)
puts temp.fahrenheit  # => 86.0

クラスメソッドは特定のインスタンスに依存しない処理に、インスタンスメソッドは特定のインスタンスのデータを使う処理に適しています。

終わりに

ここまでお読み頂きありがとうございました!!それでは2章の記事でまたお会いしましょう!

追記(2025年月4月9日)

本記事の2章目となるクラスとオブジェクトに関する記事を執筆しました!ぜひお読みくださいませ!!
https://zenn.dev/osakayakyu/articles/1ae86adc9e8e60

Discussion