As we've explored in Protocol I article, protocols define a blueprint of methods, properties, and other requirements for particular functionalities. When developing complex applications, you may want to combine multiple protocols to create more flexible and reusable components. This is where protocol composition becomes extremely useful.
What is Protocol Composition?
Protocol composition is the ability to combine multiple protocols into a single type requirement. By doing this, you can require that a type conforms to multiple protocols without defining a new protocol or type. This approach keeps your codebase lightweight and flexible, making it easier to add, change, or remove requirements as your application grows.
In Swift, you can create a protocol composition by using the &
operator. This operator lets you specify that a type should conform to multiple protocols, such as ProtocolA & ProtocolB
. Here’s a simple example:
protocol Drivable {
func drive()
}
protocol Fuelable {
var fuelLevel: Int { get }
}
func startTrip(vehicle: Drivable & Fuelable) {
if vehicle.fuelLevel > 0 {
vehicle.drive()
print("Trip started!")
} else {
print("Not enough fuel to start the trip.")
}
}
In this example, startTrip
accepts any type that conforms to both Drivable
and Fuelable
. The function checks the fuel level and starts the trip only if there is enough fuel, utilizing the methods and properties from both protocols.
So the objects we can use here have to conform both protocols:
struct Plane: Drivable, Fuelable {
let fuelLevel: Int = 100
func drive() {
print("Driving plane")
}
}
When to Use Protocol Composition
Protocol composition is especially useful when you want to define a function or method that operates on types with multiple behaviors, but without introducing a new, single protocol that may be too specific. Some common scenarios include:
- Combining Similar Behavior: If two protocols provide complementary behavior, like data-fetching and parsing, you might want a function to operate on objects that handle both tasks.
- Decoupling Code: Protocol composition allows you to create flexible APIs without needing a rigid class hierarchy.
- Reducing Complexity: Protocol composition can simplify the type requirements in generic functions or methods by avoiding complex class inheritance.
Using Protocol Composition with Associated Types
If a protocol has an associated type, it cannot be directly composed with other protocols. However, by introducing generic constraints, you can work around this limitation to compose protocols that have associated types. Here’s an example:
protocol Identifiable {
associatedtype ID
var id: ID { get }
}
protocol Displayable {
var displayName: String { get }
}
struct User: Identifiable, Displayable {
var id: Int
var displayName: String
}
func showDisplayName<T: Identifiable & Displayable>(for item: T) {
print("Displaying: \(item.displayName)")
}
In this example, showDisplayName
can be used with any type that conforms to both Identifiable
and Displayable
, making it useful for displaying information for different identifiable items.
Protocol Composition with Extensions
You can also extend a protocol composition to add default implementations or additional methods. For example:
protocol Movable {
func move()
}
protocol Stoppable {
func stop()
}
extension Movable & Stoppable {
func startMovingAndThenStop() {
move()
print("Moving...")
stop()
print("Stopped.")
}
}
Now, any type that conforms to both Movable
and Stoppable
will automatically get the startMovingAndThenStop
method, reducing code duplication and promoting reuse.
Benefits of Protocol Composition
Protocol composition is a powerful tool in Swift that brings numerous benefits:
- Increased Code Reusability: You can build flexible APIs that operate on types conforming to multiple protocols, enabling you to write reusable code for different types.
- Improved Type Safety: By specifying multiple protocol conformances, you ensure that types meet all required functionality, enhancing type safety in your code.
- Decoupled and Modular Design: Protocol composition encourages a more modular approach, allowing you to decouple implementations and design smaller, specialized protocols.
Be the first to comment