Abstract Class vs. Protocol-Oriented Approach in Swift
A Comprehensive Comparison with Examples
Swift, Apple’s versatile and modern programming language, offers developers two distinct paradigms for creating reusable and flexible code: abstract classes and protocol-oriented programming. In this blog post, we’ll explore these approaches in-depth, showcasing their differences and providing real-world examples to help you choose the right strategy for your projects. The intention of this article is for you to have enough information to make the right decision when deciding one over another.
Abstract Classes: The Foundation of Hierarchy
Abstract classes serve as a blueprint for creating related classes, allowing you to define common attributes and methods while leaving some details to be implemented by subclasses. Abstract classes cannot be instantiated directly; instead, they provide a template for subclasses to follow
Let's start by taking an example. Say you have an abstract class that has a method call that fetches data for you in a generic way.
class Fetcher<Model> {
func fetch(from url: URL) async throws -> Model {
fatalError("menthod not implemented")
}
}
class FeedFetcher:Fetcher<Feed> {
override func fetch(from url: URL) async throws -> Feed {
// here goes your implementation
}
}
Now if we were to use this abstract class to enforce a shared interface, we will implement the method fetch
in our base classes.
Protocol-Oriented Approach: Embracing Flexibility
Protocol-oriented programming (POP) is a design paradigm in Swift that promotes the use of protocols to define behaviors that types can conform to. This approach encourages composition and modularization, enabling a higher degree of flexibility.
To continue to build on the example we started above. Let’s see how does a protocol-oriented approach look like
protocol Fetcher {
associatedtype Model
func fetch(from url: URL) async throws -> Model
}
class FeedFetcher: Fetcher {
func fetch(from url: URL) async throws -> Feed {
// here goes your implementation
}
}
Question: So as a developer how do you decide what to use ???
Answer: It depends.
Both abstract classes and protocol approaches have something to offer depending on your needs. I have tried to differentiate them based on some key aspects
Key Differences and Use Cases
- Inheritance vs. Composition
- Abstract classes use inheritance to share common attributes and behaviors among subclasses.
- Protocol-oriented programming employs composition by allowing types to conform to multiple protocols, promoting more modular and reusable code.
2. Multiple Inheritance
- Abstract classes support single inheritance, meaning a subclass can only inherit from one parent class.
- Protocols enable multiple inheritance, allowing a type to conform to multiple protocols, thus facilitating a more flexible structure.
3. Default Implementations
- Protocols can provide default implementations for methods and properties, allowing conforming types to adopt or override them selectively.
4. Type Flexibility
- Abstract classes offer a more rigid type hierarchy, while protocols provide greater flexibility and extensibility.
What about combining both abstract and protocol approaches?
typealias Fetchable<Model> = FetchableBase<Model> & FetchableProtocol
protocol FetchableProtocol {
associatedtype Model
func fetch(from url: URL) async throws -> Model
}
class FetchableBase<Model> {
let networking: Networking
init(networking: Networking) {
self.networking = networking
}
}
class FeedLoader: Fetchable<Feed>{
}
As you see above, we tried to combine the best of both worlds by taking the following steps
- First, move out the stored property
networking
in the class declaration and move the function only in the protocol - Second, making type alias
Fetchable<Model>
where we combined the protocol and class adherence in a single declaration.
Conclusion
When deciding between abstract classes and protocol-oriented programming in Swift, consider your project’s requirements and design goals. Abstract classes are suitable for hierarchical structures where a shared base class provides a blueprint for subclasses. On the other hand, protocol-oriented programming excels in scenarios that demand flexibility, composition, and a higher degree of reusability.
By grasping the nuances of both approaches and harnessing their strengths, you can architect code that is robust, maintainable, and aligned with your development objectives. Whether you lean towards abstract classes for well-defined hierarchies or embrace protocol-oriented programming for its adaptability, Swift empowers you to create elegant and efficient solutions for a wide range of programming challenges.