LoginSignup
25
15

More than 5 years have passed since last update.

【swift】依存性逆転の原則についてまとめる

Last updated at Posted at 2018-07-28

依存性逆転の原則(Dependency Inversion Principle)は以下になる。

・上位のモジュールは下位のモジュールに依存してはならない。どちらのモジュールも「抽象」に依存すべきである
・「抽象」は実装の詳細に依存してはならない。実装の詳細が「抽象」に依存すべきである

色々試行錯誤したり質問したりしてなんとなく理解できたので、まとめていく。

上位のモジュールは下位のモジュールに依存してはならない。どちらのモジュールも「抽象」に依存すべきである

上位のモジュールが下位モジュールに依存しているパターン

depend.swift
// 適当に選んで、残りのパラメーター捨ててる
struct User {
    let login: String
    let id:Int
    let url: URL
    let score: Double
}

// 下位モジュール
class GitHubSearchRepository {
    func getUsers(query: String, completion: @escaping ([User]) -> Void) {
        // なんらかの実装
    }
}

// 上位モジュール
class SearchUsecase {

    let repository = GitHubSearchRepository() // 実装に依存

    func searchUsers() {
        repository.getUsers(query: "apple") { users in
            // なんらかの実装
        }

    }
}

問題点

GitHubSearchRepositryに変更が入ったり、別の実装を利用したくなった場合に、Usecase側のコードを修正する必要がある。

つまり修正が閉じていない。

依存性逆転させたパターン

Dip.swift
// 抽象
protocol GitHubSearchRepositoryProtocol {
    func getUsers(query: String, completion: @escaping ([User]) -> Void)
}

// 上位モジュール
// 実際はこのユースケースもDipSearchUsecaseProtocolとかに適合している
class DipSearchUsecase {

    let repository: GitHubSearchRepositoryProtocol // 抽象に依存

    init(repository: GitHubSearchRepositoryProtocol) {
        self.repository = repository
    }

    func searchUsers() {
        repository.getUsers(query: "apple") { users in
            // なんらかの実装
        }
    }
}

// 下位モジュール
class GitHubSearchRepositoryV3: GitHubSearchRepositoryProtocol { // 抽象へ依存

    func getUsers(query: String, completion: @escaping ([User]) -> Void) {
        // GitHub API3 での実装
    }

}

// 下位モジュール
class GitHubSearchRepositoryV4: GitHubSearchRepositoryProtocol { // 抽象へ依存

    func getUsers(query: String, completion: @escaping ([User]) -> Void) {
        // GitHub API4 での実装
        // API v4はまだないが、でた場合は実装が破壊的かもしれない。
        // しかし、実装が抽象へ依存しているので、DIを切り替えるだけで変更できる
    }

}

/*
DIする
*/

let repository = GitHubSearchRepositoryV3()
// ver4を使うときは、 let repository = GitHubSearchRepositoryV4() とするだけ
let usecase = DipSearchUsecase(repository: repository)

良い点

上位モジュールが下位モジュールの実装に依存しておらず抽象依存しているため、修正が閉じている。
外部から適切な依存性注入(Dependency Injection)を利用すれば良いだけ。

上位のモジュールも下位モジュールも、お互いの都合を考える必要がなくなった。

「抽象」は実装の詳細に依存してはならない。実装の詳細が「抽象」に依存すべきである

この文章は理解が難しかった。
しかし、ここでいう「依存」という言葉の定義を、オブジェクト指向で利用される「使用関係」を表す依存の意味ではなく、一般的な

他のものにたよって成立・存在すること。

という意味で理解するとうまく消化できた。

つまり、

GitHubSearchRepositoryProtocol が、GitHubSearchRepositoryV4 の実装によって成り立つ

ではなく

GitHubSearchRepositoryV4 が GitHubSearchRepositoryProtocol の宣言によって成り立つ。

という関係を作るのがDIP。

DIPでは実装に合わせてインターフェースを変えるのではなく、インターフェースに実装を合わせる。

利用したコード

PlaygroundをGithubに置きました
https://github.com/syatyo/DIP/tree/master

参考・関連

オブジェクト指向設計原則とは

swiftで 依存関係逆転の原則 を使ってテストしやすい設計にする

25
15
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
25
15