There exist a number of articles/blogs explaining the Dependency Inversion Principle (DIP) using Swift; to name a few (top Google hits):
- GitHub/ochococo/OOD-Principles-In-Swift - The Dependency Inversion Principle,
- clean-swift.com / Dependency Inversion – A Little Swifty Architecture
Now, Swift is widely described (even by Apple) as a protocol-oriented programming language, rather that an OOP language; so naturally these articles realize the abstractions in DIP using protocols rather than inheritance, however specifically heterogeneous protocols to allow the policy layer to use polymorphism to invoke lower layers without needing to know any implementation details. E.g.:
// Example A
protocol Service {
func work()
}
final class Policy {
// Use heterogeneous protocol polymorphically.
private let service: Service
init(service: Service) { self.service = service }
func doWork() { service.work() }
}
final class SpecificService: Service {
func work() { /* ... */ print("Specific work ...") }
}
let policy = Policy(service: SpecificService())
// Resolves to doWork() of SpecificService at runtime
policy.doWork() // Specific work ...
I realize polymorphism is one of the key concepts in DIP (unless I'm mistaken), but from a Swift implementation perspective, I'd rather see DIP applied using protocol-constrained generics rather than runtime polymorphism. E.g.:
// Example B
protocol Service {
func work()
}
final class Policy<PolicyService: Service> {
private let service: PolicyService
init(service: PolicyService) { self.service = service }
func doWork() { service.work() }
}
final class SpecificService: Service {
func work() { /* ... */ print("Specific work ...") }
}
let policy = Policy(service: SpecificService())
// policy.service specialized as SpecificService instance at compile time
policy.doWork() // Specific work ...
I haven't seen anyone use generics in the context of DIP and Swift, so I'm probably the one in the dark here, hence this question.
As a design principle, I believe B above achieves the same goal as A, w.r.t. DIP; and when applied in a statically typed protocol-oriented programming language that generally prefers composition over inheritance, specifically (afaik) protocols and generics over protocols and polymorphism, I would prefer using B. This is naturally under the constraint that we only ever use a single specialized Policy
at a time, and fall back on DIP to ease changes in the low-level details by decoupling/dependency inversion.
Question: Would Example B above be considered a valid application of DIP, even if a specialized Policy
"knows" about the concrete Service
at compile time (due to generics; still de-coupled by abstractions applied as constraints to the generic placeholder)?