Principios SOLID en Swift — guía práctica
Los cinco principios SOLID con explicaciones cortas y ejemplos aplicados a Swift.
S — Responsabilidad única
Una clase o módulo debería tener una única razón para cambiar. Evita mezclar peticiones a API, persistencia y presentación en un mismo ViewModel.
protocol OrderReading {
func loadOrders() async throws -> [Order]
}
struct LoadOrdersUseCase {
let repository: any OrderReading
func execute() async throws -> [Order] {
try await repository.loadOrders()
}
}O — Abierto/cerrado
Extiende el comportamiento mediante protocolos e implementaciones nuevas sin modificar constantemente código estable. Añade métodos de pago sin un switch creciente.
protocol PaymentMethod {
func charge(_ amount: Decimal) async throws
}
struct CardMethod: PaymentMethod {
func charge(_ amount: Decimal) async throws {
// Charge using the card provider.
}
}
struct TransferMethod: PaymentMethod {
func charge(_ amount: Decimal) async throws {
// Charge using bank transfer.
}
}L — Sustitución de Liskov
Una implementación debe poder sustituir otra respetando las expectativas del contrato. Un mock que silencie errores y devuelva una lista vacía puede romper esa garantía.
struct Order { let id: String }
protocol OrderReading {
func load() async throws -> [Order]
}
enum OrderError: Error { case unavailable }
struct FailingOrders: OrderReading {
func load() async throws -> [Order] {
throw OrderError.unavailable
}
}
// Returning [] instead would hide an error expected by callers.I — Segregación de interfaces
Evita protocolos gigantes: separa OrderReading de OrderWriting si cada consumidor necesita solo una parte de sus métodos.
struct Order { let id: String }
protocol OrderReading {
func loadOrders() async throws -> [Order]
}
protocol OrderWriting {
func save(_ order: Order) async throws
}
struct OrderList {
let reader: any OrderReading
}D — Inversión de dependencias
Las capas de alto nivel dependen de abstracciones, no de implementaciones concretas. Un caso de uso recibe el protocolo y la implementación se decide en el composition root.
struct Order { let id: String }
protocol OrderReading {
func loadOrders() async throws -> [Order]
}
struct LoadOrdersUseCase {
let repository: any OrderReading
func execute() async throws -> [Order] {
try await repository.loadOrders()
}
}| Principio | Pregunta clave | Pista práctica |
|---|---|---|
| SRP | ¿Cuántos motivos de cambio tiene? | Separar API, persistencia e interfaz. |
| OCP | ¿Puedo ampliar sin modificar la lógica estable? | Implementaciones de protocolos. |
| LSP | ¿Puedo sustituir una implementación sin romper expectativas? | Respetar semántica de errores. |
| ISP | ¿Dependo de funciones que no utilizo? | Protocolos pequeños. |
| DIP | ¿El dominio conoce la infraestructura concreta? | Inyectar abstracciones. |
Trade-off: los protocolos son útiles en límites de arquitectura y tests, pero crearlos para todas las clases añade indirectamente complejidad sin mejorar necesariamente el diseño.
Notas personales de estudio. Verifica los matices según la versión de Swift/iOS y la documentación oficial.