依存性逆転の原則(Dependency Inversion Principle)は以下になる。
・上位のモジュールは下位のモジュールに依存してはならない。どちらのモジュールも「抽象」に依存すべきである
・「抽象」は実装の詳細に依存してはならない。実装の詳細が「抽象」に依存すべきである
色々試行錯誤したり質問したりしてなんとなく理解できたので、まとめていく。
上位のモジュールは下位のモジュールに依存してはならない。どちらのモジュールも「抽象」に依存すべきである
上位のモジュールが下位モジュールに依存しているパターン
// 適当に選んで、残りのパラメーター捨ててる
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側のコードを修正する必要がある。
つまり修正が閉じていない。
依存性逆転させたパターン
// 抽象
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