Skip to content

Instantly share code, notes, and snippets.

@d108
Last active June 26, 2023 23:01
Show Gist options
  • Save d108/815a6a996a4c25ac8be6c9970c8273e6 to your computer and use it in GitHub Desktop.
Save d108/815a6a996a4c25ac8be6c9970c8273e6 to your computer and use it in GitHub Desktop.

Dependency Inversion Principle Summary

  1. Creating concrete dependencies can go against SOLID principles and lead to technical debt.

  2. The principle of dependency inversion matches the "D" in SOLID and goes by "DIP" for short.

  3. Nipping embedded concrete dependencies in the bud can save time and money in the long run.

At the outset of a new project, it can be difficult to anticipate the costs associated with allowing concrete dependencies to become deeply embedded in the codebase -- particularly when the primary challenge is simply getting the project off the ground. However, over time, as the use of concrete types increases, technical debt can accumulate and compound. It can drag down the development process and make it more difficult to add new features, fix bugs, and deliver value to customers.

In this article, we will explore how to address technical debt from the outset of development by using the power of dependency inversion. To illustrate these concepts, I will draw from my own work experience with a real-world example.

We use a charting library, Charts by Daniel Gindi (Swift package version 4.1.0), to display line chart data in our iOS and macOS apps for tracking heart rate data. It is open-source and available on GitHub, and has equivalent functionality as an Android library for the same purpose.

Chart data consists of ChartDataEntry. It is a concrete type representing a single data point on a chart. It has three properties we use: x, y, and data.

  • The x property is a Double that represents the x-value of the data point.

  • The y property is a Double that represents the y-value of the data point.

  • The data property is an optional "Any" representing additional data associated with the data point.

To prevent concreting our use of chart data, we can create an abstract interface that represents the data we need to display on a chart by first creating a protocol.

protocol ChartDataProtocol
{
    associatedtype MeasurementDate
    var x: Double { get set }
    var y: Double { get set }
    var data: MeasurementDate { get set }
}

The power of the associated type in the protocol lets us elegantly specify the data property. We can then create a concrete implementation of this interface that conforms to the protocol.

struct ChartDataProvider: ChartDataProtocol
{
    typealias MeasurementDate = Date
    var x: Double
    var y: Double
    var data: MeasurementDate
}

Finally, in the few places where we need to depend on the actual concrete type, we can transform the abstract type into the concrete type with a one-line function in an extension.

import Charts

extension ChartDataProvider
{
    /// - returns: A ChartDataEntry object with values from a ChartDataType struct.
    func toChartDataEntry() -> ChartDataEntry
    {
        ChartDataEntry(x: x, y: y, data: data)
    }
}

This small effort allows us to work with chart data entries abstractly, without having to repeatedly import the Charts library into our codebase. Making abstraction a habit can help us avoid technical debt and increase flexibility in the long run. It also allows us to easily swap out the Charts library for another library in the future, if we so choose.

Navigating Dependency Inversion with Protocols and Polymorphism in Swift

Investing in minor upfront planning of abstraction can pay off in the long run by reducing the cost of change. This is particularly true when the project is likely to undergo significant changes in the future. In the context of a charting library, the primary purpose of charting is presentation. Therefore, it may be preferable to avoid hard dependencies on presentation code and instead prioritize loose coupling between UI functionality and computation. This is particularly important given that UI tends to undergo dramatic changes over time, like with SwiftUI.

Swift protocols and polymorphism are two powerful tools for implementing the Dependency Inversion Principle (DIP), one of the foundational principles of SOLID software architecture.

  1. Protocols as Abstractions: In Swift, protocols act as a blueprint of methods, properties, and other requirements. They provide a layer of abstraction between high-level and low-level modules in a system. High-level modules define protocols that low-level modules conform to. This way, high-level modules are not directly dependent on low-level modules - instead, they both depend on abstractions.

  2. Polymorphism: Polymorphism allows us to treat different types as the same general type. In Swift, this means that a function or method can accept parameters of a protocol type, working with any type that conforms to the protocol. This allows high-level modules to remain decoupled from the specific type of low-level modules.

  3. Protocol Extensions: Swift allows default implementations of methods and computed properties in protocols using protocol extensions. This helps reduce the amount of boilerplate code and allows for better abstraction of behavior that might be common to several classes.

  4. Protocol Composition: Swift supports combining multiple protocols together. A type can conform to multiple protocols, and a function or method can work with parameters of a composite protocol type. This provides a high level of flexibility and allows a class to be built from reusable components.

  5. Associated Types: Protocols in Swift support associated types, providing a way to use a placeholder type in protocols that can be defined when the protocol is adopted. This gives us another tool for creating flexible and reusable code components.

Through these features, Swift protocols and polymorphism offer an elegant and efficient way to implement the Dependency Inversion Principle, allowing us to write more flexible, reusable, and maintainable code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment