SOLID:原則、OOPの5つのルールと開発での応用

著者: IT Sectr 公開日: 2026-05-11 読了時間: 10 分

SOLID — 2000年代初頭にRobert C. Martin(Uncle Bob)によって提唱された、オブジェクト指向プログラミングの5つの原則です。DigitalOcean(2024年)によると、SOLIDはSingle Responsibility、Open-Closed、Liskov Substitution、Interface Segregation、Dependency Inversionの頭文字を取ったものです。これらの原則はクリーンアーキテクチャの基礎を形成し、Android開発(MVP、MVVM、Clean Architecture)やiOS(VIPER、TCA)で適用されています。

重要ポイント

  • SOLID — 5つのOOP原則(SRP、OCP、LSP、ISP、DIP)の頭字語で、柔軟で保守しやすいコードを作成するためにRobert C. Martinが提唱しました。
  • SRP(Single Responsibility) — 各クラスは変更理由が1つだけ、モジュールごとに1つの責任を持ちます。
  • OCP(Open-Closed) — クラスは拡張に対して開かれ、修正に対して閉じられており、継承とポリモーフィズムを通じて実現されます。
  • LSP(Liskov Substitution) — サブクラスのオブジェクトは、プログラムの正当性を変えることなく基底クラスのオブジェクトを置き換えられるべきです。
  • ISP(Interface Segregation) — クライアントは使用しないインターフェースに依存すべきではなく、インターフェースは狭く特化されるべきです。
  • DIP(Dependency Inversion) — 高レベルモジュールは低レベルモジュールに依存せず、両方とも抽象化に依存します。

SOLIDとは?5つの原則の概要

SOLID — オブジェクト指向設計の5つの原則を表す記憶術的な頭字語です。この用語はRobert C. Martinが記事「Design Principles and Design Patterns」(2000年)で紹介し、後に書籍「Agile Software Development: Principles, Patterns, and Practices」(2002年)で広められました。SOLIDはフレームワークやライブラリではなく、コードの結合度を低くし、テストしやすく、変更しやすくするためのプラクティスの集合です。

Clean Coder Blog(2014年)によると、各SOLID原則は特定の設計問題を解決します。SRPは神クラスと戦い、OCPは連鎖的な変更を防ぎ、LSPは不正確な継承から保護し、ISPは大きなインターフェースを避け、DIPは強い結合を減らします。これらは一緒になってクリーンアーキテクチャの基盤を形成し、AndroidプロジェクトでMVP、MVVM、MVIとともに使用されています。

SRP:単一責任の原則

Single Responsibility Principle(SRP) — 単一責任の原則。定式化:「クラスに変更理由が1つだけあるべき」というものです。つまり、各モジュールやクラスは、正確に1つの機能または1つのドメインエンティティに対して責任を持ちます。クラスがユーザー管理とメール送信の両方を扱う場合、変更理由が2つあるため、SRPに違反します。

Robert C. Martin(2002年)によると、SRPは最も重要でありながら最も違反されている原則です。モバイル開発では、Activity/FragmentでUIロジック、ナビゲーション、ネットワーキング、ビジネスロジックを混在させることでSRPがしばしば違反されます。解決策は各レイヤーを別々のクラスに抽出することです。UIロジックはViewModel、データはRepository、ナビゲーションはNavControllerが担当します。

SRPの例:UserManagerの分割

プロファイルの読み込み、設定の保存、メール送信を行うUserManagerクラスを考えてみましょう。これらは3つの異なる責任であり、それぞれ別々のクラスに抽出する必要があります。UserProfileRepository(読み込み)、UserSettingsStorage(保存)、EmailService(送信)です。クライアントコード(ViewModel)は依存性注入を通じて3つすべてを使用し、各クラスは独立して簡単にテストでき、他に影響を与えることなく変更できます。

kotlin
// ❌ SRP違反:Activityがネットワーク、DB、UIを把握
class ProfileActivity : AppCompatActivity() {
    fun loadProfile() {
        api.getUser() // ネットワーク呼び出し
        db.saveUser()    // DB操作
        updateUI()         // UI更新
    }
}

// ✅ SRP遵守:レイヤーが分離されている
class ProfileViewModel : ViewModel() {
    private val repo = UserRepository()
    fun loadProfile() { repo.getUser() }
}

SRP違反の兆候:200行を超えるクラス、異なるドメインのメソッド、異なる理由での頻繁な変更。Android開発のルールはシンプルです。Activityは画面のライフサイクルだけを処理し、ViewModelはUI状態を処理し、Repositoryはデータソースを処理します。

SRPとマイクロサービスアーキテクチャ

SRP原則はクラスだけでなく、サービスレベルのアーキテクチャにも適用されます。各マイクロサービスは1つのドメインエンティティを処理します。UserService — ユーザーのみ、PaymentService — 支払いのみ、NotificationService — 通知のみ。これにより、サービスの独立したスケーリング、デプロイ、テストが可能になります。モバイルアプリケーションでは、マイクロサービスレベルでのSRPは、APIクライアントをドメインごとに分離することで現れます。

OCP:オープン・クローズドの原則

Open-Closed Principle(OCP) — クラスは拡張に対して開かれ(新しい振る舞いを追加できる)、修正に対して閉じられる(既存のコードは変更されない)べきです。これはポリモーフィズム、抽象クラス、インターフェースを通じて達成されます。既存のメソッドにif-elseを追加する代わりに、新しいインターフェースの実装が作成されます。

Clean Coder Blog(2014年)によると、OCPはStrategyパターンと組み合わせることで最も効果的です。例えば、アプリが異なる支払い方法(Google Pay、Apple Pay、PayPal)をサポートする場合、支払いプロセッサにswitch-caseを追加する必要はありません。各支払い方法は共通のPaymentGatewayインターフェースを実装し、新しい支払いシステムは既存のコードを変更せずに新しいクラスとして追加されます。

kotlin
// ✅ OCP:拡張に開かれ、修正に閉じられている
interface PaymentGateway {
    fun processPayment(amount: Double): Boolean
}

class GooglePayGateway : PaymentGateway {
    override fun processPayment(amount: Double) = true
}

// 新しい決済システム — 既存コードの変更なし
class ApplePayGateway : PaymentGateway {
    override fun processPayment(amount: Double) = true
}

LSP:リスコフの置換原則

Liskov Substitution Principle(LSP) — Barbara Liskovの置換原則。SがTのサブタイプである場合、T型のオブジェクトはプログラムのプロパティを変更することなくS型のオブジェクトで置き換えられます。形式的には:基底クラスを使用する関数は、その任意のサブクラスでも正しく動作する必要があります。基底クラスがスローしない例外をサブクラスがスローする場合、LSPに違反しています。

Robert C. Martin(2002年)によると、LSPはSOLID原則の中で最も理解が難しいものです。古典的な違反例は、Rectangleを継承するSquareクラスです。SquareのsetWidthが幅と高さの両方を設定する場合、Rectangleの振る舞いを期待するクライアントコードは予期しない結果を得ます。モバイル開発では、ViewModelを継承する際にLSPがしばしば違反されます — 子ViewModelが必須の依存関係を追加する場合です。

kotlin
// ❌ LSP違反:SquareがRectangleの振る舞いを壊す
open class Rectangle(open var width: Int, open var height: Int)

class Square(side: Int) : Rectangle(side, side) {
    override var width
        get() = super.width
        set(value) { super.setBoth(value, value) }
}

ISP:インターフェース分離の原則

Interface Segregation Principle(ISP) — クライアントは使用しないインターフェースに依存すべきではありません。1つの「太った」インターフェースの代わりに、複数の狭く特化したインターフェースを作成します。クラスがインターフェースを実装しているが、一部のメソッドがUnsupportedOperationExceptionをスローしたり空のままだったりする場合、それはISP違反の明確な兆候です。

DigitalOcean(2024年)によると、ISPはViewModelとRepositoryを設計する際にモバイル開発で特に重要です。すべてのCRUDメソッドを持つ1つのUserRepositoryインターフェースの代わりに、QueryUserRepository(読み取り専用)とCommandUserRepository(書き込み)を作成する方が良いでしょう。そうすれば、読み取り専用のクライアント(UI要素)はQueryインターフェースのみに依存し、書き込みメソッドについて何も知る必要がありません。

kotlin
// ❌ 大きなインターフェース — クライアントが不要なメソッドを強制的に実装
interface UserOperations {
    fun getUser(id: String): User
    fun saveUser(user: User)
    fun deleteUser(id: String)
    fun exportUsers(): File
}

// ✅ ISP:分離されたインターフェース
interface UserReader { fun getUser(id: String): User }
interface UserWriter { fun saveUser(user: User) }
interface UserDeleter { fun deleteUser(id: String) }

DIP:依存性逆転の原則

Dependency Inversion Principle(DIP) — 高レベルモジュールは低レベルモジュールに依存すべきではありません。両方とも抽象化(インターフェース)に依存すべきです。抽象化は詳細に依存すべきではなく、詳細が抽象化に依存すべきです。これは「依存性注入」(DI)ではありませんが、DIはDIPを実装する一般的な方法です。

Robert C. Martin(2019年)によると、DIPはクリーンアーキテクチャの基盤です。ViewModel(高レベル)は直接RetrofitApi(詳細)のインスタンスを作成すべきではありません。代わりに、ViewModelはUserRepositoryインターフェースに依存し、Retrofitを使用した具体的なUserRepositoryImplがコンストラクタを通じて渡されます。Androidでは、DIPはHilt/DaggerやKoinを通じて実装されます。すべての依存関係はDIコンテナを介して提供されます。

kotlin
// ✅ DIP:Moduleは抽象化に依存し、詳細に依存しない
class UserRepositoryImpl(
    private val api: UserApi,   // インターフェースに依存
    private val db: UserDao     // インターフェースに依存
) : UserRepository {

    override suspend fun getUser(id: String): User {
        return api.fetchUser(id)
    }
}

// Hilt DI:詳細はDIモジュールを通じて配線される
@Module
object NetworkModule {
    @Provides
    fun provideUserApi(retrofit: Retrofit): UserApi =
        retrofit.create(UserApi::class.java)
}

モバイル開発におけるSOLIDの適用

モバイル開発におけるSOLIDは、アプリケーションアーキテクチャから個々のクラスまで、すべてのレベルで適用されます。Androidプロジェクトでは、クリーンアーキテクチャがコードを3つのレイヤーに分割します。domain(ビジネスロジック — フレームワークに依存しない)、data(リポジトリ、API、DB)、presentation(UI、ViewModel)です。domainレイヤーはSOLID原則を使用します。ユースケース(SRP)、リポジトリインターフェース(DIP)、エンティティクラス(OCP + LSP)です。

Android Developers Guide(2025年)によると、AndroidでのSRPはViewModel、Repository、Mapperの分離に現れます。OCP — DataSourceインターフェースを通じた新しいデータソースの追加時。LSP — 異なるリポジトリ間での統一的なResult処理。ISP — CQRSアプローチ(Read/Writeリポジトリの分離)。DIP — 依存性注入のためのHilt/Koin。

原則なしでの問題モバイルプロジェクトでの解決策
SRP1000+行のActivityViewModel + UseCase + Repository
OCP支払いタイプによるswitch-caseStrategy:PaymentGatewayインターフェース
LSPBaseViewModel置き換え時のバグサブクラスの契約を確認
ISPUnsupportedOperationExceptionReader / Writerの分離
DIPViewModelが手動でRetrofitを作成Hilt / Koin DIコンテナ

SOLID適用時のよくある間違い

SOLIDの間違いは、ほとんどの場合、コードの過度な複雑化に関連しています。1つ目は — コンテキストを考慮せずに原則を文字通りに従うこと。たった1つの「クリーンな」ISPのために、1つのUserServiceクラスを10のインターフェースと15のクラスに分割するのは過剰設計です。SOLIDはツールであって、目的ではありません。2つ目の間違いは — SRPを「1メソッド=1責任」と混同すること。すべてが同じ責任領域に属している限り、クラスは複数のメソッドを持つことができます。

Simple Thread(2024年)によると、3つ目の間違い — AndroidでViewModelを継承する際にLSPを無視すること。基底ViewModelがLiveDataを期待しているのに子がStateFlowを使用する場合、LiveDataに購読しているクライアントコードは更新を受け取りません。4つ目 — テストのためにDIPに違反すること。RepositoryImplが直接OkHttpClientのインスタンスを作成し、単体テストを不可能にします。

黄金律:実際の問題(頻繁な変更、テストの難しさ、重複)を解決する場合にのみSOLIDを適用してください。単純なCRUD画面では、5つの原則すべてに厳密に従うことは過剰です。ビジネスロジック、金融計算、API連携にはSOLIDが不可欠です。

SOLIDとクリーンアーキテクチャの関係

クリーンアーキテクチャ(Robert C. Martin、2012年) — アプリケーションレイヤーレベルでのSOLIDの直接的な適用です。SRPはユースケースの境界を定義します(各ユースケース — 1つのクラス)。OCPはリポジトリインターフェースを通じて実装されます(Data LayerはDomainを変更せずに変更可能)。ISPはユースケースを入力/出力境界に分離します。DIP — 依存関係の方向をDomainレイヤーの内側に向けます。LSPは、任意のリポジトリ実装がユースケースを壊すことなく置き換え可能であることを保証します。

よくある質問

SOLIDを簡単に言うと何ですか?

SOLID — コードを変更・テスト・理解しやすくするための5つのルールです。各文字が1つの原則を表します。大きなクラスを書かない(SRP)、既存のコードを変更せず新しいものを追加する(OCP)、サブクラスの振る舞いを壊さない(LSP)などです。

最も重要なSOLID原則はどれですか?

SRP(Single Responsibility)が最も重要と考えられています。その違反は神クラス — テストや変更が難しい巨大なクラス — につながるからです。しかし、DIP(Dependency Inversion)なしではコードは強い結合のままとなり、それもまた重要です。

モバイル開発にSOLIDは必須ですか?

必須ではありませんが、ライフサイクルの長い商用プロジェクトには強く推奨されます。シンプルなアプリ(1画面、ビジネスロジックなし)にはSOLIDは過剰かもしれません。50画面以上、3人以上の開発者がいるプロジェクトでは、SOLIDは最低限必要なものです。

SOLIDに従わないとどうなりますか?

結果:クラスが「太って」(1000行以上)、1か所の変更が他の3か所を壊し、単体テストが書けなくなり、新機能の追加に日数の代わりに数週間かかります。時間とともに、コードは「Big Ball of Mud」— 絡み合ってもろい状態 — になります。

プロジェクトでSOLIDが守られているか確認するには?

遵守の兆候:各クラスが200行未満、機能の変更が5ファイル以上に影響しない、10の依存関係をモックせずにテストが書ける、新しい開発者が1日で構造を理解できる。SonarQubeやdetektなどのツールはSRPやDIPの違反を特定するのに役立ちます。

まとめ

  • SOLID — 柔軟で保守しやすいコードを作成するための5つのOOP原則(SRP、OCP、LSP、ISP、DIP)
  • SRP — 各エンティティは1つのタスクに責任を持ち、神クラスの問題を解決
  • OCP — ポリモーフィズムによる拡張、既存コードの修正ではない
  • LSP — サブクラスは基底クラスの振る舞いを壊してはいけない
  • ISP — 万能「スイスアーミーナイフ」の代わりに狭いインターフェース
  • DIP — 抽象化への依存、AndroidではHilt/Koinによる注入
  • SOLIDはクリーンアーキテクチャと商用モバイルプロジェクトに不可欠

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください