Introdução: Por que SOLID é importante para um desenvolvedor Swift
O desenvolvimento para iOS e macOS exige não apenas conhecimento de frameworks, mas também a capacidade de criar uma arquitetura sustentável, testável e escalável. É aqui que os princípios SOLID entram em cena — cinco regras fundamentais de design orientado a objetos, formuladas por Robert Martin (Tio Bob).
Neste artigo, vamos analisar cada um dos cinco princípios SOLID com exemplos de código em Swift, mostrar antipadrões típicos e sugerir soluções corretas. Após a leitura, você será capaz de escrever código que é fácil de manter, estender e testar.
1. Single Responsibility Principle (SRP) — Princípio da Responsabilidade Única
Formulação: Uma classe deve ter apenas uma razão para mudar. Em outras palavras, cada módulo é responsável por exatamente uma funcionalidade.
Problema: Classe-monstro
Considere um exemplo típico do desenvolvimento iOS — a classe UserManager, que faz demais:
class UserManager { func fetchUser() -> User { /* requisição de rede */ } func saveUserToDatabase(_ user: User) { /* trabalho com CoreData */ } func formatUserDisplayName(_ user: User) -> String { /* formatação */ } func sendWelcomeEmail(to user: User) { /* envio de email */ }}Aqui, a classe é responsável por requisições de rede, banco de dados, formatação de UI e envio de emails. Qualquer alteração em um desses aspectos levará a uma mudança no UserManager.
Solução: Separação de responsabilidades
class UserNetworkService { func fetchUser() -> User { /* apenas requisição de rede */ }}
class UserDatabaseService { func saveUser(_ user: User) { /* apenas trabalho com BD */ }}
class UserFormatter { func displayName(for user: User) -> String { /* apenas formatação */ }}
class EmailService { func sendWelcome(to user: User) { /* apenas envio de email */ }}Agora, cada classe tem uma responsabilidade clara, o que simplifica os testes e a modificação.
2. Open/Closed Principle (OCP) — Princípio do Aberto/Fechado
Formulação: Entidades de software devem estar abertas para extensão, mas fechadas para modificação.
Problema: Lógica rígida
Suponha que temos uma classe para calcular descontos:
class DiscountCalculator { func calculateDiscount(for order: Order, customerType: String) -> Double { if customerType == "regular" { return order.total * 0.05 } else if customerType == "premium" { return order.total * 0.1 } else if customerType == "vip" { return order.total * 0.2 } return 0 }}Ao adicionar um novo tipo de cliente (gold), será necessário modificar a classe existente, o que viola o OCP.
Solução: Protocolos e polimorfismo
protocol DiscountStrategy { func discount(for order: Order) -> Double}
struct RegularDiscount: DiscountStrategy { func discount(for order: Order) -> Double { order.total * 0.05 }}
struct PremiumDiscount: DiscountStrategy { func discount(for order: Order) -> Double { order.total * 0.1 }}
struct VIPDiscount: DiscountStrategy { func discount(for order: Order) -> Double { order.total * 0.2 }}
class DiscountCalculator { func calculateDiscount(for order: Order, using strategy: DiscountStrategy) -> Double { return strategy.discount(for: order) }}Agora, para adicionar um novo desconto, basta criar uma nova estrutura que implemente o protocolo DiscountStrategy, sem modificar o DiscountCalculator.
3. Liskov Substitution Principle (LSP) — Princípio da Substituição de Barbara Liskov
Formulação: ```