Lesson 77Protocols
Protocol-Oriented Programming
What is POP?
Protocol-Oriented Programming is Swift's preferred paradigm. Instead of inheriting from a base class, you compose behavior by conforming to multiple protocols!
OOP vs POP
OOP Problems
- • Single inheritance only
- • Tight coupling
- • Fragile base class problem
- • Reference types only
POP Benefits
- • Multiple conformance
- • Loose coupling
- • Composition over inheritance
- • Value types supported
Key POP Patterns
Protocol Composition
Combine protocols with &
Flyable & SwimmableDefault Implementations
Share code via extensions
Retroactive Modeling
Add protocols to existing types
Dependency Injection
Swap implementations easily
Apple's Mantra
"Start with a protocol"
Design your APIs around protocols first, then provide implementations. This makes code more testable and flexible!
When to Use Classes
- Need reference semantics
- Need deinitializers
- Interop with Objective-C
- Identity is important (===)
main.swift
// PROTOCOL-ORIENTED PROGRAMMING (POP)
// Swift's preferred paradigm over traditional OOP
// TRADITIONAL OOP APPROACH
class Animal {
func eat() { print("Eating") }
}
class Bird: Animal {
func fly() { print("Flying") }
}
class Penguin: Bird {
// Problem: Penguins can't fly but inherit fly()!
}
// PROTOCOL-ORIENTED APPROACH
protocol Eatable {
func eat()
}
protocol Flyable {
func fly()
}
protocol Swimmable {
func swim()
}
// Default implementations
extension Eatable {
func eat() { print("Eating food") }
}
extension Flyable {
func fly() { print("Flying in the sky") }
}
extension Swimmable {
func swim() { print("Swimming in water") }
}
// Compose behaviors as needed!
struct Sparrow: Eatable, Flyable { }
struct PenguinPOP: Eatable, Swimmable { } // No fly!
struct Duck: Eatable, Flyable, Swimmable { }
let sparrow = Sparrow()
sparrow.eat() // Eating food
sparrow.fly() // Flying in the sky
let penguin = PenguinPOP()
penguin.eat() // Eating food
penguin.swim() // Swimming in water
// penguin.fly() // Error! No fly method
// PROTOCOL COMPOSITION
typealias FlyingSwimmer = Flyable & Swimmable
func describeFlyingSwimmer(_ creature: FlyingSwimmer) {
creature.fly()
creature.swim()
}
describeFlyingSwimmer(Duck())
// BUILDING REUSABLE COMPONENTS
protocol Identifiable {
var id: String { get }
}
protocol Validatable {
func validate() -> Bool
}
protocol Persistable {
func save()
func delete()
}
// Default implementations
extension Identifiable {
var id: String { UUID().uuidString }
}
extension Persistable {
func save() {
print("Saving to database...")
}
func delete() {
print("Deleting from database...")
}
}
// Compose as needed
struct User: Identifiable, Validatable, Persistable {
var email: String
func validate() -> Bool {
return email.contains("@")
}
}
// VALUE SEMANTICS WITH PROTOCOLS
protocol Copyable {
func copy() -> Self
}
struct Document: Copyable {
var title: String
var content: String
func copy() -> Document {
return Document(title: "Copy of \(title)", content: content)
}
}
// GENERIC CONSTRAINTS WITH PROTOCOLS
func process<T: Identifiable & Validatable>(_ item: T) {
print("Processing item: \(item.id)")
if item.validate() {
print("Item is valid!")
}
}
// RETROACTIVE MODELING
// Add protocol conformance to existing types
extension Int: Identifiable {
var id: String { String(self) }
}
extension String: Validatable {
func validate() -> Bool {
return !isEmpty
}
}
// PROTOCOL WITNESSES
protocol Renderer {
associatedtype Output
func render() -> Output
}
struct HTMLRenderer: Renderer {
func render() -> String {
return "<html>...</html>"
}
}
struct JSONRenderer: Renderer {
func render() -> [String: Any] {
return ["status": "ok"]
}
}
// DEPENDENCY INJECTION WITH PROTOCOLS
protocol NetworkService {
func fetch(url: String) async -> Data?
}
struct RealNetworkService: NetworkService {
func fetch(url: String) async -> Data? {
// Real network call
return nil
}
}
struct MockNetworkService: NetworkService {
func fetch(url: String) async -> Data? {
// Return mock data for testing
return "Mock data".data(using: .utf8)
}
}
class ViewModel {
let networkService: NetworkService
init(networkService: NetworkService) {
self.networkService = networkService
}
}
// Use real service in production
let prodVM = ViewModel(networkService: RealNetworkService())
// Use mock service in tests
let testVM = ViewModel(networkService: MockNetworkService())Try It Yourself!
Design a game character system using POP. Create protocols for Attackable, Defendable, Healable and compose different character types!