Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Dependency Injection: why should I care?

Dependency Injection: why should I care?

Let's talk about Dependency Injection and how it could help you to keep your code clean.

Avatar for miserya

miserya

May 21, 2021
Tweet

More Decks by miserya

Other Decks in Programming

Transcript

  1. What is dependency? Class X depends on class Y if:

    • X has a Y (for ex. as a property) • X is a Y (for ex. inherits from Y) • X depends on Z that depends on Y.
  2. What is dependency? Class X depends on class Y if:

    • X has a Y (for ex. as a property) • X is a Y (for ex. inherits from Y) • X depends on Z that depends on Y. How to remove dependency? • Create interface I (protocol) with Y’s methods and properties, used by X. • Y conforms to I. • X uses object of I type.
  3. What is dependency? Class X depends on class Y if:

    • X has a Y (for ex. as a property) • X is a Y (for ex. inherits from Y) • X depends on Z that depends on Y. How to remove dependency? • Create interface I (protocol) with Y’s methods and properties, used by X. • Y conforms to I. • X uses object of I type. Inversion of Control This is
  4. protocol LoginService { func login(with username: String, password: String) }

    protocol BiometricsManager { func validateUser() -> Result<Void, Error> } class LoginViewModel { private let loginService: LoginService private let biometricsManager: BiometricsManager init(loginService: LoginService, biometricsManager: BiometricsManager) { self.loginService = loginService self.biometricsManager = biometricsManager } } Constructor injection • LoginService and BiometricsManager - are Services. • LoginViewModel is Client.
  5. protocol LoginService { func login(with username: String, password: String) }

    protocol BiometricsManager { func validateUser() -> Result<Void, Error> } class LoginViewModel { private let loginService: LoginService private let biometricsManager: BiometricsManager init(loginService: LoginService, biometricsManager: BiometricsManager) { self.loginService = loginService self.biometricsManager = biometricsManager } } Constructor injection • LoginService and BiometricsManager - are Services. • LoginViewModel is Client. It’s easy to understand class dependencies Init method could have too many arguments
  6. protocol LoginService { func login(with username: String, password: String) }

    protocol BiometricsManager { func validateUser() -> Result<Void, Error> } class LoginViewModel { private var loginService: LoginService? private var biometricsManager: BiometricsManager? func setLoginService(_ loginService: LoginService) { self.loginService = loginService } func setBiometricsManager(_ biometricsManager: BiometricsManager) { self.biometricsManager = biometricsManager } } Setter injection • LoginService and BiometricsManager - are Services. • LoginViewModel is Client.
  7. protocol LoginService { func login(with username: String, password: String) }

    protocol BiometricsManager { func validateUser() -> Result<Void, Error> } class LoginViewModel { private var loginService: LoginService? private var biometricsManager: BiometricsManager? func setLoginService(_ loginService: LoginService) { self.loginService = loginService } func setBiometricsManager(_ biometricsManager: BiometricsManager) { self.biometricsManager = biometricsManager } } Setter injection • LoginService and BiometricsManager - are Services. • LoginViewModel is Client. It’s a good approach when you only have a few dependencies it’s easy to forget to set a necessary dependency
  8. protocol LoginService { func login(with username: String, password: String) }

    protocol BiometricsManager { func validateUser() -> Result<Void, Error> } protocol LoginServiceInjectable { func setLoginService(_ loginService: LoginService) } protocol BiometricsManagerInjectable { func setBiometricManager(_ biometricsManager: BiometricsManager) } class LoginViewModel: LoginServiceInjectable, BiometricsManagerInjectable { var loginService: LoginService? var biometricsManager: BiometricsManager? func setLoginService(_ loginService: LoginService) { self.loginService = loginService } func setBiometricManager(_ biometricsManager: BiometricsManager{ self.biometricsManager = biometricsManager } } Interface injection • LoginServiceInjectable and BiometricsManagerInjectable - are Services. • LoginViewModel is Client.
  9. protocol LoginService { func login(with username: String, password: String) }

    protocol BiometricsManager { func validateUser() -> Result<Void, Error> } protocol LoginServiceInjectable { func setLoginService(_ loginService: LoginService) } protocol BiometricsManagerInjectable { func setBiometricManager(_ biometricsManager: BiometricsManager) } class LoginViewModel: LoginServiceInjectable, BiometricsManagerInjectable { var loginService: LoginService? var biometricsManager: BiometricsManager? func setLoginService(_ loginService: LoginService) { self.loginService = loginService } func setBiometricManager(_ biometricsManager: BiometricsManager{ self.biometricsManager = biometricsManager } } Interface injection • LoginServiceInjectable and BiometricsManagerInjectable - are Services. • LoginViewModel is Client. an injector can be completely unaware of the client’s actual implementation it’s easy to forget to set a necessary dependency
  10. protocol LoginService { func login(with username: String, password: String) }

    class LoginServiceImpl: LoginService { ... } protocol LoginServiceInjectable { var loginService: LoginService { get } } extension LoginServiceInjectable { var loginService: LoginService { LoginServiceInjector.loginService } } enum LoginServiceInjector { public private(set) static var loginService: LoginService = LoginServiceImpl() } // Same implementation of BiometricsManagerInjectable as LoginServiceInjectable class LoginViewModel: LoginServiceInjectable, BiometricsManagerInjectable { var error: Error? func login() { switch biometricsManager.validateUser() { case .success(): loginService.login() case .failure(let error): self.error = error } } } Interface injection • LoginServiceInjectable and BiometricsManagerInjectable - are Services. • LoginViewModel is Client. an injector can be completely unaware of the client’s actual implementation it’s di ff icult to see whole Client interface
  11. Swinject Cleanse Resolver Needle Min deployment target • iOS 9.0+

    • Mac OS X 10.10+ • watchOS 2.0+ • tvOS 9.0+ Installation • Cocoapods • Carthage • SPM Storyboard integration + SwiftUI Generic type registration problems (ObservableObject, View, etc.) + / - + Easy to understand and use - No Generics support - The dependency graph is created at runtime - Dependency is registered at runtime
  12. Swinject Cleanse Resolver Needle Min deployment target • iOS 9.0+

    • Mac OS X 10.10+ • watchOS 2.0+ • tvOS 9.0+ • iOS 8.3+ • macOS 10.10+ Installation • Cocoapods • Carthage • SPM • Cocoapods • Carthage • SPM Storyboard integration + - SwiftUI Generic type registration problems (ObservableObject, View, etc.) Components must have reference type only, Views could be Modules. + / - + Easy to understand and use - No Generics support - The dependency graph is created at runtime - Dependency is registered at runtime + generic types + powerful - A little bit complicated to start - The dependency graph is created at runtime - Dependency is registered at runtime
  13. Swinject Cleanse Resolver Needle Min deployment target • iOS 9.0+

    • Mac OS X 10.10+ • watchOS 2.0+ • tvOS 9.0+ • iOS 8.3+ • macOS 10.10+ • iOS 11.0+ • Mac OS X 10.14+ • tvOS 11.0+ Installation • Cocoapods • Carthage • SPM • Cocoapods • Carthage • SPM • Cocoapods • Carthage • SPM Storyboard integration + - + SwiftUI Generic type registration problems (ObservableObject, View, etc.) Components must have reference type only, Views could be Modules. Generic type registration problems (ObservableObject, View, etc.) + / - + Easy to understand and use - No Generics support - The dependency graph is created at runtime - Dependency is registered at runtime + generic types + powerful - A little bit complicated to start - The dependency graph is created at runtime - Dependency is registered at runtime + faster than Swinject + Easy to understand and use + property wrappers - property wrapper combination problems - The dependency graph is created at runtime - Dependency is registered at runtime
  14. Swinject Cleanse Resolver Needle Min deployment target • iOS 9.0+

    • Mac OS X 10.10+ • watchOS 2.0+ • tvOS 9.0+ • iOS 8.3+ • macOS 10.10+ • iOS 11.0+ • Mac OS X 10.14+ • tvOS 11.0+ • iOS 9.0+ • macOS 10.10 Installation • Cocoapods • Carthage • SPM • Cocoapods • Carthage • SPM • Cocoapods • Carthage • SPM • Cocoapods • Carthage • SPM • Homebrew (for Code Generator) Storyboard integration + - + - SwiftUI Generic type registration problems (ObservableObject, View, etc.) Components must have reference type only, Views could be Modules. Generic type registration problems (ObservableObject, View, etc.) Generic type registration problems (ObservableObject, View, etc.) + / - + Easy to understand and use - No Generics support - The dependency graph is created at runtime - Dependency is registered at runtime + generic types + powerful - A little bit complicated to start - The dependency graph is created at runtime - Dependency is registered at runtime + faster than Swinject + Easy to understand and use + property wrappers - property wrapper combination problems - The dependency graph is created at runtime - Dependency is registered at runtime + Compile-time safe + Displays missing dependencies - will be used along with Code Generator