Railsで学ぶオープン・クローズド原則 〜 拡張に強く、修正に優しい設計を目指して
はじめに
Railsアプリを開発していて、こんなことに悩んだことはありませんか?
- 「既存の処理に条件を追加したら、別の場所が壊れた」
- 「仕様変更が入るたびにコードを直接書き換える必要がある」
- 「if文がどんどん増えて可読性が落ちている」
これらは、設計原則「OCP(オープン・クローズド原則)」を意識することで改善できます。
本記事では、Railsでこの原則をどのように実践するかを、具象クラスと抽象化、Strategyパターンやモジュールの活用といった手法を交えて解説します。
OCP(オープン・クローズド原則)とは
OCP(Open/Closed Principle)は、SOLID原則のひとつで、次のように定義されています:
「ソフトウェアの構成要素は拡張に対して開かれており、修正に対して閉じていなければならない」
これは、既存のコードを変更せずに、新しい振る舞いを追加できるように設計するという考え方です。
OCP違反の例:条件分岐の増殖
❌ Before
以下のように、配送方法によって送料を計算するロジックがあるとします。
class ShippingFeeCalculator
def initialize(order)
@order = order
end
def calculate
case @order.shipping_method
when 'standard'
500
when 'express'
1000
when 'pickup'
0
else
raise "Unknown shipping method"
end
end
end
- 条件が増えるたびにこのクラスを修正する必要がある
- 他の場所で同じ判定が必要になったら、ロジックを重複させるか共通化する必要がある
OCPを意識した改善
✅ After:Strategyパターンの導入
class ShippingFeeCalculator
def initialize(order)
@strategy = strategy_for(order.shipping_method)
end
def calculate
@strategy.calculate
end
private
def strategy_for(method)
case method
when 'standard' then StandardShipping.new
when 'express' then ExpressShipping.new
when 'pickup' then PickupShipping.new
else
raise "Unknown shipping method"
end
end
end
class StandardShipping
def calculate
500
end
end
class ExpressShipping
def calculate
1000
end
end
class PickupShipping
def calculate
0
end
end
- 拡張(新しい配送方法の追加)は新しいクラスを追加するだけで済む
-
ShippingFeeCalculator
自体は一切変更する必要がない
RailsでOCPを実践するテクニック
1. モジュールを使って振る舞いを切り替える(Concernによる拡張の分離)
OCPにおける「変更に強くする」一つの方法として、モデルに直接書かず、関心ごとをモジュールに分離する手法があります。
たとえば、複数のモデルが「承認機能」を持つ場合、approve!
の実装を各モデルに持たせると変更のたびに全モデルを修正する必要があります。
❌ Before:それぞれのモデルに重複コード
class Comment < ApplicationRecord
def approve!
update!(approved: true, approved_at: Time.current)
end
end
class Article < ApplicationRecord
def approve!
update!(approved: true, approved_at: Time.current)
end
end
✅ After:Concernで共通の振る舞いに切り出す
# app/models/concerns/approvable.rb
module Approvable
extend ActiveSupport::Concern
def approve!
update!(approved: true, approved_at: Time.current)
end
end
class Comment < ApplicationRecord
include Approvable
end
class Article < ApplicationRecord
include Approvable
end
-
approve!
の仕様が変わった場合でも、Concern内を修正するだけで済む - 各モデルは拡張に開かれており、修正はConcern内で完結するためOCPを守れる
OCPを守るメリット
観点 | OCPを守らない場合 | OCPを守った場合 |
---|---|---|
拡張性 | 条件を追加するたびに既存クラスを修正する | 新しいクラスを追加するだけで済む |
テストのしやすさ | 巨大なクラスをテストする必要がある | クラスごとにテストが分離できる |
バグの発生率 | 修正によって他の処理を壊すリスクが高まる | 既存コードを触らないので壊れにくい |
チーム開発 | 同じクラスを複数人で編集 → コンフリクト発生 | 新規クラス追加で並行開発しやすい |
まとめ
- OCPは「既存のコードを変更せずに拡張する」ための設計原則
- RailsでもサービスオブジェクトやStrategyパターン、モジュールを使えば実現できる
- 特に「今後拡張が予想される処理」にはOCPを意識すると、保守性・再利用性が大きく向上する
ぜひあなたのRailsアプリでも、OCPを意識した設計に取り組んでみてください。