|
// Deps2.playground |
|
|
|
import UIKit |
|
|
|
// Design issues / decisions: |
|
// |
|
// - should "providing" protocols use funcs or vars? |
|
// - i.e. `var serviceA` vs `func serviceA()` |
|
// |
|
// - for deps which themselves have deps ("internal nodes of the deps tree"), |
|
// should they take a deps object or should we ban them from using the deps pattern? |
|
// - i.e. if C depends on A and B, should we `class ServiceC { init(deps: Deps) }` |
|
// or should we `class ServiceC { init(a: ServiceA, b: ServiceB) }`? |
|
// |
|
// - if internal nodes take a deps object, the deps object will run into a bootstrapping problem |
|
// (`ServiceC.init()` needs a `Deps` and `Deps.init()` needs a `ServiceC`) |
|
// - we can get around this by using force-unwrapped vars (and a static builder method) |
|
// - or avoid the issue by banning internal nodes from using the deps pattern. |
|
// |
|
// - if internal nodes are banned from the deps pattern, we lose the ability to replace |
|
// a dep at run-time and have every part of the system automatically start using the new |
|
// dep (if ServiceC uses ServiceA (given via init), replacing Deps.serviceA |
|
// at runtime with a fake does not affect ServiceC, which will still use the original A). |
|
|
|
|
|
|
|
// This playground explores: |
|
// - "providing" protocols use funcs |
|
// - "internal" nodes do NOT use deps pattern |
|
// - (there is no bootstrapping problem, but internal dep nodes don't get updated deps) |
|
|
|
|
|
// This approach suffers from the fact that "internal" dep nodes have to be manually updated if deps change. |
|
|
|
|
|
// service data types |
|
|
|
public typealias A = Int |
|
public typealias B = Int |
|
public typealias C = (A,B) |
|
|
|
|
|
|
|
// service protocols |
|
|
|
public protocol AServicing { |
|
func getAs() -> [A] |
|
} |
|
|
|
public protocol BServicing { |
|
func getBs() -> [B] |
|
} |
|
|
|
public protocol CServicing { |
|
func getCs() -> [C] |
|
} |
|
|
|
|
|
|
|
// service implementations |
|
|
|
public class AService: AServicing { |
|
public func getAs() -> [A] { |
|
return [1,2,3] |
|
} |
|
} |
|
|
|
public class BService: BServicing { |
|
public func getBs() -> [B] { |
|
return [11,12,13] |
|
} |
|
} |
|
|
|
public class CService: CServicing { |
|
|
|
public init(aService: AServicing, bService: BServicing) { |
|
_aService = aService |
|
_bService = bService |
|
} |
|
|
|
public func getCs() -> [C] { |
|
return Array(zip(_aService.getAs(), _bService.getBs())) |
|
} |
|
|
|
private let _aService: AServicing |
|
private let _bService: BServicing |
|
} |
|
|
|
|
|
|
|
// deps protocols |
|
|
|
public protocol AServiceProviding { |
|
var aService: AServicing { get } |
|
} |
|
|
|
public protocol BServiceProviding { |
|
var bService: BServicing { get } |
|
} |
|
|
|
public protocol CServiceProviding { |
|
var cService: CServicing { get } |
|
} |
|
|
|
|
|
|
|
// deps implementation |
|
|
|
public class Deps: AServiceProviding, BServiceProviding, CServiceProviding { |
|
public var aService: AServicing |
|
public var bService: BServicing |
|
public var cService: CServicing |
|
|
|
public init(aService: AServicing, bService: BServicing, cService: CServicing) { |
|
self.aService = aService |
|
self.bService = bService |
|
self.cService = cService |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
// Some objects which use the deps: |
|
|
|
public class ControllerAB { |
|
public let deps: AServiceProviding & BServiceProviding |
|
public init(deps: AServiceProviding & BServiceProviding) { |
|
self.deps = deps |
|
} |
|
|
|
public func total() -> Int { |
|
return (deps.aService.getAs() + deps.bService.getBs()) |
|
.reduce(0, { $0 + $1 }) |
|
} |
|
} |
|
|
|
public class ControllerC { |
|
public let deps: CServiceProviding |
|
public init(deps: CServiceProviding) { |
|
self.deps = deps |
|
} |
|
|
|
public func total() -> Int { |
|
return deps.cService.getCs() |
|
.reduce(0, { $0 + $1.0 + $1.1 }) |
|
} |
|
} |
|
|
|
|
|
|
|
// use real services: |
|
|
|
let a = AService() |
|
a.getAs() // [1,2,3] |
|
|
|
let b = BService() |
|
b.getBs() // [11,12,13] |
|
|
|
let c = CService(aService: a, bService: b) |
|
c.getCs() // [(1,11),(2,12),(3,13)] |
|
|
|
let deps1 = Deps(aService: a, bService: b, cService: c) |
|
|
|
let controllerab1 = ControllerAB(deps: deps1) |
|
controllerab1.total() // 42 |
|
|
|
let controllerc1 = ControllerC(deps: deps1) |
|
controllerc1.total() // 42 |
|
|
|
|
|
|
|
// fake A and B implementations: |
|
|
|
public class FakeAService: AServicing { |
|
public func getAs() -> [A] { |
|
return [-1,-2,-3] |
|
} |
|
} |
|
|
|
public class FakeBService: BServicing { |
|
public func getBs() -> [B] { |
|
return [-11,-12,-13] |
|
} |
|
} |
|
|
|
|
|
|
|
// use fake A with real C: |
|
|
|
let fa = FakeAService() |
|
fa.getAs() // [-1,-2,-3] |
|
|
|
let c2 = CService(aService: fa, bService: b) |
|
c2.getCs() // [(-1,11),(-2,12),(-3,13)] |
|
|
|
let deps2 = Deps(aService: fa, bService: b, cService: c2) |
|
deps2.cService.getCs() // [(-1,11),(-2,12),(-3,13)] |
|
|
|
let controllerab2 = ControllerAB(deps: deps2) |
|
controllerab2.total() // 30 |
|
|
|
let controllerc2 = ControllerC(deps: deps2) |
|
controllerc2.total() // 30 |
|
|
|
|
|
|
|
// fake B with real C: |
|
|
|
let fb = FakeBService() |
|
fb.getBs() // [-11,-12,-13] |
|
|
|
let c3 = CService(aService: a, bService: fb) |
|
c3.getCs() // [(1,-11),(2,-12),(3,-13)] |
|
|
|
let deps3 = Deps(aService: a, bService: fb, cService: c3) |
|
deps3.cService.getCs() // [(1,-11),(2,-12),(3,-13)] |
|
|
|
let controller3 = ControllerAB(deps: deps3) |
|
controller3.total() // -30 |
|
|
|
let controllerc3 = ControllerC(deps: deps3) |
|
controllerc3.total() // -30 |
|
|
|
|
|
|
|
// fake A and fake B with real C: |
|
|
|
let c4 = CService(aService: fa, bService: fb) |
|
c4.getCs() // [(-1,-11),(-2,-12),(-3,-13)] |
|
|
|
let deps4 = Deps(aService: fa, bService: fb, cService: c4) |
|
deps4.cService.getCs() // [(-1,-11),(-2,-12),(-3,-13)] |
|
|
|
let controllerab4 = ControllerAB(deps: deps4) |
|
controllerab4.total() // -42 |
|
|
|
let controllerc4 = ControllerC(deps: deps4) |
|
controllerc4.total() // -42 |
|
|
|
|
|
|
|
// or use a fake C directly: |
|
|
|
public class FakeCService: CServicing { |
|
public func getCs() -> [C] { |
|
return [(0,0)] |
|
} |
|
} |
|
|
|
let fc = FakeCService() |
|
fc.getCs() // [(0,0)] |
|
|
|
let deps5 = Deps(aService: a, bService: b, cService: fc) |
|
deps5.aService.getAs() // [1,2,3] |
|
deps5.bService.getBs() // [11,12,13] |
|
deps5.cService.getCs() // [(0,0)] |
|
|
|
let controllerab5 = ControllerAB(deps: deps5) |
|
controllerab5.total() // 42 |
|
|
|
let controllerc5 = ControllerC(deps: deps5) |
|
controllerc5.total() // 0 |
|
|
|
|
|
// problem: updating Deps with a fake A doesn't affect it's C: |
|
|
|
let deps6 = Deps(aService: a, bService: b, cService: c) |
|
deps6.aService.getAs() // [1,2,3] |
|
deps6.cService.getCs() // [(1,11),(2,12),(3,13)] |
|
deps6.aService = fa |
|
deps6.aService.getAs() // [-1,-2,-3] |
|
deps6.cService.getCs() // [(1,11),(2,12),(3,13)] |
|
// we have to manually update C: |
|
deps6.cService = CService(aService: fa, bService: b) |
|
deps6.cService.getCs() // [(-1,11),(-2,12),(-3,13)] |