設計原則 レッスン1
単一責任の原則(SRP)
クラスやモジュールが持つ責任を1つに絞ることで変更に強い設計を作るSRPの考え方を学ぶ
SRPとは何か
単一責任の原則(Single Responsibility Principle)は、 「クラスを変更する理由は1つであるべき」という原則です。ここでの「責任」とは 機能の数ではなく、誰の要求で、なぜそのクラスを変更することになるかという変更理由の単位を指します。
ポイント
「1つのメソッドしか持たないクラスにすべき」という意味ではありません。 関連する複数のメソッドが同じ理由で変更されるなら、それは1つの責任として1つのクラスにまとめてよいのです。
SRP違反の例
次の疑似コードでは、1つのクラスが「注文の計算ロジック」と「レシートの印字フォーマット」という 2つの異なる変更理由を抱えてしまっています。
// 悪い例: 2つの異なる変更理由が混在している
class Order {
calculateTotal(items) {
// 税率や割引ルールが変わるたびに変更される
return items.reduce((sum, item) => sum + item.price * item.quantity, 0)
}
printReceipt(items) {
// レシートのフォーマットが変わるたびに変更される
let text = "--- 領収書 ---\n"
for (const item of items) {
text += item.name + " x" + item.quantity + "\n"
}
return text
}
}「税率の計算ロジックを変えたい経理担当の都合」と「レシートの見た目を変えたい店舗運用の都合」は、 まったく別の理由から発生する変更です。この2つが同じクラスに同居していると、 一方の変更がもう一方に予期せぬ影響を与えるリスクが生まれます。
責任を分離した改善例
// 良い例: 変更理由ごとにクラスを分離する
class OrderCalculator {
calculateTotal(items) {
return items.reduce((sum, item) => sum + item.price * item.quantity, 0)
}
}
class ReceiptPrinter {
print(items) {
let text = "--- 領収書 ---\n"
for (const item of items) {
text += item.name + " x" + item.quantity + "\n"
}
return text
}
}こうしておくことで、税率計算のロジックを変更してもレシートのフォーマットには影響せず、 それぞれを独立してテストすることもできます。
SRPを適用するメリットと注意点
メリット
- • 変更の影響範囲が予測しやすくなる
- • テストが書きやすくなる(1つの責任だけを検証すればよい)
- • クラスの役割が名前から推測しやすくなる
注意点
- • 分割しすぎるとクラス間の呼び出しが増え、逆に追いにくくなる
- • 「変更理由が本当に別か」を見極めることが重要(形だけの分割は意味がない)
- • チームの規模やドメインの複雑さに応じて適切な粒度は変わる
SRPを適用する際に自分に問いかけるとよい質問は、 「このクラスを変更しなければならなくなるとしたら、それはどんな理由が考えられるか? その理由は1つに絞れているか?」です。
ポイント
- • SRPにおける「責任」は、クラスを変更する理由の単位を指す
- • 異なる変更理由が1つのクラスに混在すると、片方の変更がもう片方に予期せぬ影響を与える
- • 変更理由ごとにクラスを分離することで、影響範囲が予測しやすくテストもしやすくなる
- • 分割しすぎると追いにくくなるため、「本当に別の変更理由か」を見極めることが重要
確認クイズ
1 / 3SRPにおける「責任」が意味するものはどれか?