Инверсия зависимостей с ассоциированным типом в протоколе

#swift #xcode #dependency-injection #dependency-inversion

Вопрос:

Мне трудно реализовать инверсию зависимостей. Оглядевшись вокруг, найдите фантастическую статью об удалении типа Swift. Я не уверен, как я могу получить преимущество в своей ситуации. Вот чего я пытаюсь достичь.

Протокол для создания сетей

 protocol Networkable {
    associatedtype Response: Decodable

    func request(handler: @escaping((Result<Response, Error>) -> ()))
}
 

Конкретное осуществление Networkable протокола

 final class Networker<Response: Decodable>: Networkable {
    private let session: URLSession
    private let url: URL

    init(session: URLSession, url: URL) {
        self.session = session
        self.url = url
    }

    func request(handler: @escaping ((Result<Response, Error>) -> ())) {
        session.dataTask(with: url) { data, response, error in
            handler(.success(try! JSONDecoder().decode(Response.self, from: data!)))
        }
    }
}
 

A ViewModel , который зависит от Networkable протокола

 class ViewModel {
    let networker: Networkable
    // Error-> Protocol 'Networkable' can only be used as a generic constraint because it has Self or associated type requirements

    init(netwrorker: Networkable) {
        self.networker = netwrorker
    }

    func execute() {
        networker.request { _ in
            print("Responsed")
        }
    }
}
 

Использование:

 class View {
    let viewModel: ViewModel

    init() {
        let networker = Networker<ResponseModel>(session: .shared, url: URL(string: "SOME URL")!)
        self.viewModel = ViewModel(netwrorker: networker)
    }

    func load() {
        viewModel.execute()
    }
}

struct ResponseModel: Decodable {}
 

Вопросы:

  1. Является ли это правильным способом реализации инверсии зависимостей?
  2. Как я могу добиться общего поведения в сетевом классе и при этом оставаться открытым для тестирования?

Ответ №1:

Ваш ViewModel класс плохо сформирован:

 class ViewModel<T: Decodable> {
    let networker: Networker<T>
    …
}
 

В противном случае вы можете создать типизированный AnyNetworkable конкретный тип, который вы могли бы использовать, как в:

 class ViewModel<T: Decodable> {
    let networker: AnyNetworkable<T>
    …
}
 

Или вы могли бы использовать еще более общий подход, как в:

 class ViewModel<R, T: Networkable> where T.Response == R {
     let networker: T
}
 

Очевидно, то же самое отражается на вашем View типе, который вам также необходимо сделать либо общим, либо специализированным для определенного конкретного типа ViewModel .