Состав протокола для внедрения зависимости - дилемма причинности / проблема компиляции - PullRequest
1 голос
/ 13 января 2020

Я пытаюсь использовать Swift "Состав протокола" для внедрения зависимости впервые. Существуют различные сообщения в блогах от уважаемых инженеров в этой области, которые поддерживают этот подход, но я не могу получить код для компиляции, когда есть зависимости, зависящие от других зависимостей. Инициализирован экземпляр 1003 *, его нельзя использовать для инициализации дочерних зависимостей, но, наоборот, нельзя создать дочерние зависимости без конкретного экземпляра AllDependencies.

Курица и яйцо. Рок и трудное место.

Я попытаюсь привести самый простой пример, который я могу ...

protocol HasAppInfo {
    var appInfo: AppInfoProtocol { get }
}
protocol AppInfoProtocol {
    var apiKey: String { get }
}
struct AppInfo: AppInfoProtocol {
    let apiKey: String
}

protocol HasNetworking {
    var networking: NetworkingProtocol { get }
}
protocol NetworkingProtocol {
    func makeRequest()
}
class Networking: NetworkingProtocol {

    typealias Dependencies = HasAppInfo

    let dependencies: Dependencies

    init(dependencies: Dependencies) {
        self.dependencies = dependencies
    }

    func makeRequest() {
        let apiKey = self.dependencies.appInfo.apiKey
        // perform request sending API Key
        // ...
    }
}

class AllDependencies: HasAppInfo, HasNetworking {

    let appInfo: AppInfoProtocol
    let networking: NetworkingProtocol

    init() {
        self.appInfo = AppInfo(apiKey: "whatever")

        /// **********************************************************
        /// *** ERROR: Constant 'self.networking' used before being initialized
        /// **********************************************************
        self.networking = Networking(dependencies: self)
    }

}

Кажется, что возможно решить эту проблему с помощью lazy var, {get set} или mutating зависимостей, но это кажется крайне небезопасным, потому что любой код в вашей системе может изменять ваши зависимости по желанию.

Был бы признателен за понимание того, как другие решают то, что кажется довольно фундаментальной проблемой с этим подходом.

Ссылки

Ответы [ 2 ]

1 голос
/ 14 января 2020

Вы можете использовать приватный (установленный) ленивый var:

private(set) lazy var networking: NetworkingProtocol = {
    return Networking(dependencies: self)
}()
0 голосов
/ 21 января 2020

PS: я опубликовал исходный вопрос. Я уже принял ответ lazy var, это самый простой способ продвижения вперед, однако я хотел опубликовать это альтернативное решение, которое использует обобщенный c DependencyFactory на случай, если это поможет другим.

protocol Dependencies:
    HasAppInfo &
    HasNetworking
{}


class DependencyFactory {

    typealias Factory<T> = (Dependencies) -> T

    private enum DependencyState<T> {
        case registered(Factory<T>)
        case initialised(T)
    }

    private var dependencyStates = [String: Any]()

    func register<T>(_ type: T.Type, factory: @escaping Factory<T>) {
        dependencyStates[key(for: type)] = DependencyState<T>.registered(factory)
    }

    func unregister<T>(_ type: T.Type) {
        dependencyStates[key(for: type)] = nil
    }

    func resolve<T>(_ type: T.Type, dependencies: Dependencies) -> T {
        let key = self.key(for: type)

        guard let dependencyState = dependencyStates[key] as? DependencyState<T> else {
            fatalError("Attempt to access unregistered `\(type)` dependency")
        }

        switch dependencyState {

        case let .registered(factoryClosure):
            let dependency = factoryClosure(dependencies)
            dependencyStates[key] = DependencyState<T>.initialised(dependency)
            return dependency

        case let .initialised(dependency):
            return dependency

        }
    }

    private func key<T>(for type: T.Type) -> String {
        return String(reflecting: type)
    }
}


class AllDependencies: Dependencies {

    private let dependencyFactory = DependencyFactory()

    init() {
        dependencyFactory.register(AppInfoProtocol.self, factory: { dependencies in
            return AppInfo(apiKey: "whatever")
        })

        dependencyFactory.register(NetworkingProtocol.self, factory: { dependencies in
            return Networking(dependencies: dependencies)
        })
    }

    var appInfo: AppInfo {
        return dependencyFactory.resolve(AppInfoProtocol.self, dependencies: self)
    }

    var networking: Networking {
        return dependencyFactory.resolve(NetworkingProtocol.self, dependencies: self)
    }
}
...