Stage 2: プログラミングパラダイム — OOP・関数型・手続き型
なぜパラダイムを学ぶのか
「マイさん、パラダイムって何ですか?なんか難しそうな言葉ですけど……」
マイはホワイトボードにシンプルな図を描いた。
「パラダイムはプログラムをどう考えるかという枠組みのこと。建物の設計でいうと、同じ建物を作るのに『部屋の配置から考える』か、『動線から考える』か、『素材から考える』かという違い。」
「コードにも、考え方のスタイルがある。そのスタイルを理解しないと、どんな言語を使っても同じ罠にはまる。」
手続き型: 手順の記述
最も原始的なパラダイムは手続き型だ。上から下へ、順番に処理を記述する。
# 手続き型スタイル
users = User.all.to_a
active_users = []
i = 0
while i < users.length
if users[i].active? && users[i].email.present?
active_users << users[i]
end
i += 1
end
emails = []
j = 0
while j < active_users.length
emails << active_users[j].email
j += 1
end
puts emails「動くけど、何をしているか一目でわからない。変数 i, j の管理も煩雑。そして再利用もできない。」
手続き型はコンピュータの動作に近い記述ができるが、複雑さが増すにつれ管理が難しくなる。
オブジェクト指向: データと振る舞いをまとめる
オブジェクト指向(OOP)は現実世界の「もの(オブジェクト)」に例えてプログラムを考える。
# オブジェクト指向スタイル
class User
attr_reader :name, :email, :status
def initialize(name, email)
@name = name
@email = email
@status = :inactive
end
def activate!
@status = :active
self
end
def active?
@status == :active
end
def contact_email
active? ? @email : nil
end
end
# 利用側はオブジェクトのメソッドを呼ぶだけ
users = User.all
contact_emails = users.select(&:active?).map(&:email).compact「Rubyは純粋なオブジェクト指向言語。数値も文字列も、全てオブジェクト。1.class は Integer、"hello".upcase も普通に動く。」
OOPの三本柱
# 1. カプセル化: 内部実装を隠す
class BankAccount
def initialize(initial_balance)
@balance = initial_balance # 外から直接触れない
@transaction_log = []
end
def deposit(amount)
validate_amount!(amount)
@balance += amount
log_transaction(:deposit, amount)
self
end
def balance
@balance # 読み取りのみ公開
end
private
def validate_amount!(amount)
raise ArgumentError, "金額は正の数のみ" unless amount.positive?
end
def log_transaction(type, amount)
@transaction_log << { type: type, amount: amount, at: Time.current }
end
end
# 2. 継承: 共通の振る舞いを親クラスに
class Vehicle
attr_reader :make, :model, :year
def initialize(make, model, year)
@make = make
@model = model
@year = year
end
def age
Date.current.year - @year
end
def description
"#{@year} #{@make} #{@model}"
end
end
class ElectricVehicle < Vehicle
attr_reader :battery_capacity_kwh
def initialize(make, model, year, battery_capacity_kwh)
super(make, model, year)
@battery_capacity_kwh = battery_capacity_kwh
end
def estimated_range_km
battery_capacity_kwh * 6 # 概算
end
end
# 3. ポリモーフィズム: 同じメッセージ、異なる振る舞い
class PdfExporter
def export(content)
"<PDF>#{content}</PDF>"
end
end
class CsvExporter
def export(content)
content.map(&:to_csv).join("\n")
end
end
class JsonExporter
def export(content)
content.to_json
end
end
# どのエクスポーターでも同じように呼べる
def generate_report(data, exporter)
exporter.export(data)
endINFO
RubyのOOPの特徴
Rubyには**ミックスイン(Module)**という仕組みがある。多重継承の問題を避けながら、機能を共有できる。RailsのConcernもこの仕組みを使っている。
関数型: データの変換として考える
関数型パラダイムは副作用を排除し、データの変換として処理を記述する。
# 命令型(変数を変化させる)
total = 0
[1, 2, 3, 4, 5].each do |n|
total += n * 2 if n.odd?
end
puts total # 18
# 関数型(データを変換する)
result = [1, 2, 3, 4, 5]
.select(&:odd?) # [1, 3, 5]
.map { |n| n * 2 } # [2, 6, 10]
.sum # 18
puts result # 18「Rubyは純粋な関数型言語ではないけど、map, select, reduceなどの高階関数をサポートしている。これらを使うと何をするかが明確になる。」
# 関数型スタイルでのデータパイプライン
module ReportPipeline
def self.generate_monthly_report(year, month)
Order
.where(created_at: Date.new(year, month).all_month)
.completed
.includes(:user, :items)
.then { |orders| aggregate_by_category(orders) }
.then { |aggregated| apply_ranking(aggregated) }
.then { |ranked| format_for_display(ranked) }
end
def self.aggregate_by_category(orders)
orders.flat_map(&:items)
.group_by { |item| item.product.category }
.transform_values { |items| items.sum(&:total_price) }
end
def self.apply_ranking(aggregated)
aggregated.sort_by { |_, revenue| -revenue }
.each_with_index
.map { |(category, revenue), rank| { rank: rank + 1, category: category, revenue: revenue } }
end
def self.format_for_display(ranked)
ranked.map do |entry|
entry.merge(revenue_formatted: "¥#{entry[:revenue].to_s(:delimited)}")
end
end
end不変性の重要さ
# 悪い例: 副作用がある
def add_tax(prices)
prices.each_with_index do |price, i|
prices[i] = (price * 1.1).ceil # 元の配列を変更してしまう!
end
prices
end
original = [1000, 2000, 3000]
result = add_tax(original)
puts original # [1100, 2200, 3300] — 変わってしまった!
# 良い例: 副作用なし
def add_tax(prices)
prices.map { |price| (price * 1.1).ceil } # 新しい配列を返す
end
original = [1000, 2000, 3000]
result = add_tax(original)
puts original # [1000, 2000, 3000] — 変わっていない
puts result # [1100, 2200, 3300]WARNING
Railsでのよくある副作用バグ
map!, select!, reject!などの破壊的メソッドは元のコレクションを変更します。特にActiveRecordのallから取得したコレクションを破壊的に変更すると、予期しないバグの原因になります。
3つのパラダイムを使い分ける
「どれが一番いいんですか?」ヒロシが聞いた。
「優劣はない。状況によって使い分ける。」
class UserDashboardService
def initialize(user)
@user = user
end
def call
# OOP: ユーザーというオブジェクトの振る舞い
return { error: "アカウントが無効です" } unless @user.active?
# 関数型: データの変換パイプライン
recent_orders = @user.orders
.recent
.includes(:items)
.map { |order| format_order(order) }
# 手続き型: 順番に処理が必要な箇所
stats = {}
stats[:total_orders] = recent_orders.count
stats[:total_spent] = recent_orders.sum { |o| o[:total] }
stats[:favorite_category] = most_purchased_category
{
user: @user.as_json(only: [:id, :name, :email]),
orders: recent_orders,
stats: stats
}
end
private
def format_order(order)
{
id: order.id,
total: order.total_price,
items_count: order.items.count,
status: order.status,
created_at: order.created_at
}
end
def most_purchased_category
@user.orders
.flat_map(&:items)
.group_by { |item| item.product.category_name }
.max_by { |_, items| items.count }
&.first
end
endパラダイムと設計の関係
「パラダイムを理解すると、次のステップのSOLID原則がより深く理解できる。」マイが言った。
| パラダイム | 得意なこと | Rubyでの活用場面 |
|---|---|---|
| 手続き型 | シンプルなスクリプト、順次処理 | Rakeタスク、マイグレーション |
| OOP | 状態管理、ドメインモデル | ActiveRecord Model, Service Object |
| 関数型 | データ変換、副作用の排除 | map/select/reduce チェーン、Pure method |
「Rubyはマルチパラダイム言語。一つの言語で全てのスタイルが書ける。だからこそ、意識的に選択することが大切。」
Stage 2 のまとめ
「今日学んだことを整理しよう。」マイが言った。
ヒロシはノートにまとめた:
- 手続き型: 上から下へ。シンプルだが複雑さに弱い
- オブジェクト指向: データと振る舞いをまとめる。カプセル化・継承・ポリモーフィズム
- 関数型: データを変換として考える。副作用を排除し、予測可能に
「次のStageでは、OOPをより深く活用するためのSOLID原則を学ぶ。クラス設計の本質に踏み込んでいく。」
ヒロシはRubyのオブジェクト指向の豊かさを、初めて実感した気がした。