Introdução aos princípios SOLID em Swift
\nTodo desenvolvedor iOS, mais cedo ou mais tarde, enfrenta o problema do "código espaguete": classes incham, mudanças em um lugar quebram todo o aplicativo e os testes se tornam um pesadelo. A solução para esses problemas está na base do design orientado a objetos — os princípios SOLID. Essas cinco regras, formuladas por Robert Martin, ajudam a criar código flexível, sustentável e escalável em Swift. Neste artigo, vamos analisar cada princípio, da teoria aos exemplos práticos para iOS/macOS.
\nIndependentemente de você estar escrevendo em UIKit, SwiftUI ou AppKit, SOLID é uma base universal. Vamos mergulhar nos detalhes.
\n\n1. Single Responsibility Principle (SRP) — Princípio da Responsabilidade Única
\nTeoria
\nUma classe deve ter apenas um motivo para mudar. Em outras palavras, cada módulo é responsável por exatamente uma funcionalidade. Se o seu ViewController gerencia a UI, processa requisições de rede e faz parsing de JSON — ele viola o SRP.
\nPrática em Swift
\nVamos considerar um exemplo típico — carregar e exibir dados do usuário.
\n// ❌ Ruim: violação do SRP\nclass UserViewController: UIViewController {\n func loadUser() {\n // Requisição de rede\n let url = URL(string: "https://api.example.com/user")!\n URLSession.shared.dataTask(with: url) { data, _, _ in\n let user = try? JSONDecoder().decode(User.self, from: data!)\n DispatchQueue.main.async {\n self.updateUI(with: user)\n }\n }.resume()\n }\n \n func updateUI(with user: User?) {\n // Atualização da UI\n }\n}\n// ✅ Bom: separando responsabilidades\nclass UserService {\n func fetchUser(completion: @escaping (User?) -> Void) {\n let url = URL(string: "https://api.example.com/user")!\n URLSession.shared.dataTask(with: url) { data, _, _ in\n let user = try? JSONDecoder().decode(User.self, from: data!)\n completion(user)\n }.resume()\n }\n}\n\nclass UserViewController: UIViewController {\n let userService = UserService()\n \n func loadUser() {\n userService.fetchUser { [weak self] user in\n DispatchQueue.main.async {\n self?.updateUI(with: user)\n }\n }\n }\n \n private func updateUI(with user: User?) {\n // Atualização da UI\n }\n}\nConclusão: Agora UserService é responsável apenas pela rede, e UserViewController — pela UI. Mudanças na lógica de rede não afetam o controlador.
2. Open-Closed Principle (OCP) — Princípio Aberto/Fechado
\nTeoria
\nAs classes devem estar abertas para extensão, mas fechadas para modificação. Em vez de alterar o código existente, adicionamos novo código através de herança ou protocolos. Em Swift, a ferramenta ideal para isso são os protocolos e extensões.
\nPrática em Swift
\nImagine que estamos escrevendo um módulo para calcular descontos em uma loja online.
\n// ❌ Ruim: ao adicionar um novo desconto, é necessário modificar a classe\nclass DiscountCalculator {\n func calculate(price: Double, type: String) -> Double {\n if type == "seasonal" {\n return price * 0.9\n } else if type == "loyalty" {\n return price * 0.85\n }\n return price\n }\n}\n// ✅ Bom: usando protocolo para extensão\nprotocol DiscountStrategy {\n func applyDiscount(to price: Double) -> Double\n}\n\nstruct SeasonalDiscount: DiscountStrategy {\n func applyDiscount(to price: Double) -> Double {\n price * 0.9\n }\n}\n\nstruct LoyaltyDiscount: DiscountStrategy {\n func applyDiscount(to price: Double) -> Double {\n price * 0.85\n }\n}\n\nclass DiscountCalculator {\n func calculate(price: Double, strategy: DiscountStrategy) -> Double {\n strategy.