0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Clean Architecture】OCP: Open/Closed Principle(開放/閉鎖の原則)

Posted at

はじめに

SOLID 原則の 2 つ目は OCP(開放/閉鎖の原則) です。
これは「既存コードを壊さずに拡張できる設計」を目指す重要な原則で、変更に強い設計 の基盤になります。


1. 定義

開放/閉鎖の原則(OCP) とは:

ソフトウェアの構成要素は、拡張に対して開かれており、修正に対して閉じられているべきである。
(Open for extension, Closed for modification)

つまり:

  • 新しい機能を「追加(拡張)」できる
  • 既存の動作を壊す「修正」は最小限にする

2. 直感的な例

OCP 違反(条件分岐が増え続けるパターン)

class DiscountCalculator {
    fun applyDiscount(type: String, price: Int): Int {
        return when (type) {
            "percentage" -> (price * 0.9).toInt()
            "amount" -> (price - 100).coerceAtLeast(0)
            else -> price
        }
    }
}
  • 新しい割引タイプを追加するたびに when に修正が必要
  • 既存コードに手を入れる → バグリスク増大

OCP 準拠(拡張で対応できる設計)

interface DiscountPolicy {
    fun apply(price: Int): Int
}

class PercentageDiscount : DiscountPolicy {
    override fun apply(price: Int) = (price * 0.9).toInt()
}

class AmountDiscount : DiscountPolicy {
    override fun apply(price: Int) = (price - 100).coerceAtLeast(0)
}

// 利用側は抽象に依存
class DiscountCalculator(private val policy: DiscountPolicy) {
    fun calculate(price: Int) = policy.apply(price)
}
  • 新しい割引方式を追加する場合は DiscountPolicy を実装するだけ
  • DiscountCalculator は一切修正不要

3. OCP のメリット

  • 変更による副作用が少ない(既存コードに触らないため)
  • テスト容易性が向上(ポリシーごとに個別テストできる)
  • 再利用性が高まる(既存の利用コードはそのまま新機能を使える)
  • 保守性が向上(新しい要件に柔軟に対応可能)

4. 実務での適用パターン

  • 戦略パターン(Strategy Pattern)
    → 上記の割引ポリシーのように振る舞いを差し替える
  • DI(依存性注入)
    → 抽象に依存し、実装は注入で切り替える
  • プラグイン構造
    → 新しい機能をモジュールとして追加

5. Flutter / Android での例

Flutter(Widgetの差し替え)

abstract class ThemeColor {
  Color primary();
}

class LightTheme implements ThemeColor {
  @override
  Color primary() => Colors.blue;
}

class DarkTheme implements ThemeColor {
  @override
  Color primary() => Colors.black;
}

class ThemedButton extends StatelessWidget {
  final ThemeColor theme;
  ThemedButton(this.theme);

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      style: ElevatedButton.styleFrom(backgroundColor: theme.primary()),
      onPressed: () {},
      child: Text("OCP Example"),
    );
  }
}

Android(DI + Strategy)

interface Logger {
    fun log(message: String)
}

class ConsoleLogger : Logger {
    override fun log(message: String) = println("Console: $message")
}

class FileLogger : Logger {
    override fun log(message: String) { /* ファイル出力 */ }
}

class UserService(private val logger: Logger) {
    fun createUser(name: String) {
        logger.log("User created: $name")
    }
}

→ ログの出力先を変えても UserService は修正不要


6. よくある誤解

  • 「既存コードを一切修正してはいけない」ではない
    → インターフェース定義など「拡張のための入口」を設ける最初の修正は必要
  • 「全部を抽象化すればよい」ではない
    → 過剰な抽象は逆に複雑性を増すので、変更可能性が高い部分だけに適用

まとめ

  • OCP = 変更に強い構造
  • 「修正」ではなく「拡張」で要件対応するのが基本姿勢
  • 実務では Strategy / DI / Plugin構造 がよく使われる
  • SRP と組み合わせるとより効果的(責務を分けて、拡張で追加)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?