Na parte anterior, abordamos as três primeiras letras de SOLID: SRP·OCP·LSP. Hoje vamos resumir as duas restantes.
ISP (princípio da segregação de interfaces) e DIP (princípio da inversão de dependência).
Em especial, DIP é a raiz teórica da «injeção de dependência (DI)», que inevitavelmente encontramos ao usar bibliotecas de DI em Swift, como Swinject ou Factory. Depois de ler esta parte, você entenderá por que essas bibliotecas têm esse formato.
ISP: princípio da segregação de interfaces
Vamos começar pela definição.
Um cliente não deve ser obrigado a depender de métodos que não utiliza.
Parece complicado, mas fica claro ao observar um caso problemático. Imagine que criamos um protocolo para uma impressora multifuncional.
protocol Machine {
func print(_ doc: Document)
func scan(_ doc: Document)
func fax(_ doc: Document)
}
// A impressora antiga só imprime...
class OldPrinter: Machine {
func print(_ doc: Document) { /* Funcionamento normal */ }
func scan(_ doc: Document) { fatalError("Não escaneia") } // Implementação forçada
func fax(_ doc: Document) { fatalError("Não envia fax") } // Implementação forçada
}
É como obrigar uma impressora que só imprime a implementar também escaneamento e fax. No fim, surgem métodos falsos que lançam exceções, o que também viola o LSP que vimos na parte anterior. Uma interface inchada acaba quebrando esses dois princípios em sequência.
A solução é simples: dividir a interface por funções.
protocol Printer { func print(_ doc: Document) }
protocol Scanner { func scan(_ doc: Document) }
protocol Fax { func fax(_ doc: Document) }
class OldPrinter: Printer { ... } // Somente impressão
class MultiFunction: Printer, Scanner, Fax { ... } // Impressora multifuncional completa
Cada um promete apenas o que consegue fazer. Na prática, isso costuma aparecer assim: se, ao adotar um protocolo, surge uma implementação vazia com a sensação de «este método não tem nada a ver conosco…», é hora de dividir o protocolo.
DIP: princípio da inversão de dependência
É um princípio frequentemente mal compreendido por causa do nome. A definição tem duas linhas.
O módulo de alto nível não deve depender do módulo de baixo nível. Ambos devem depender de abstrações.
As abstrações não devem depender dos detalhes. Os detalhes devem depender das abstrações.
Vamos ver em código. Imagine um serviço de pedidos usando diretamente uma biblioteca de envio de e-mails.
// O módulo de alto nível (lógica de negócio) depende diretamente do módulo de baixo nível (detalhes de implementação)
import SendGrid
class OrderService {
private let mailer = SendGridClient(apiKey: apiKey)
func completeOrder(_ order: Order) {
// Processamento do pedido...
mailer.send(to: order.email, message: "Pedido concluído") // SendGridFica acoplado
}
}
Essa estrutura tem dois problemas. Para trocar o SendGrid por outro serviço, é preciso abrir a lógica de negócio, e cada teste envia um e-mail de verdade.
Ao aplicar DIP, a direção das setas muda.
// A abstração é definida pelo módulo de alto nível (domínio de pedidos)
protocol NotificationSender {
func send(to: String, message: String) async throws
}
class OrderService {
private let sender: NotificationSender
init(sender: NotificationSender) { // Depende apenas da abstração
self.sender = sender
}
func completeOrder(_ order: Order) async throws {
try await sender.send(to: order.email, message: "Pedido concluído")
}
}
// Detalhes(SendGrid)Seguem a abstração
class SendGridSender: NotificationSender { ... }
class SlackSender: NotificationSender { ... }
class FakeSender: NotificationSender { ... } // Para testes
Antes, a dependência apontava para «serviço de pedidos → SendGrid»; agora, tornou-se «serviço de pedidos → protocolo ← SendGrid». Como os detalhes se voltam para o domínio, isso é chamado de «inversão».
É por isso que existem frameworks de DI
Daí surge uma pergunta natural: «Então, quem cria SendGridSender()?»
Quem faz essa montagem é o contêiner de injeção de dependência (DI). É exatamente isso que bibliotecas de DI do ecossistema Swift, como Swinject e Factory, fazem. As classes dependem apenas de protocolos, e a biblioteca fica responsável por conectar as implementações concretas.
DIP é o princípio (a direção), enquanto DI é a técnica (a ferramenta) que o concretiza. Só dominar essa distinção já eleva bastante uma resposta de entrevista.
Resumo completo de SOLID
Se resumirmos o SOLID, apresentado em duas partes, em cinco linhas, fica assim.
| Princípio | Resumo em uma linha |
|---|---|
| SRP | Separe o código quando forem diferentes as pessoas que pedem mudanças |
| OCP | Responda às mudanças frequentes adicionando código em vez de modificá-lo |
| LSP | A classe filha não deve quebrar os contratos da classe pai |
| ISP | Não obrigue ninguém a implementar métodos que não utiliza |
| DIP | Não faça a lógica de negócio ficar presa aos detalhes |
No fim, os cinco apontam para a mesma direção: reduza o alcance da propagação das mudanças.
Ainda assim, aplicar esses princípios mecanicamente a todo o código pode acabar violando KISS e YAGNI. Eles são ferramentas para usar seletivamente nos pontos em que as mudanças realmente acontecem com frequência. Acredito que esse senso de equilíbrio seja a verdadeira habilidade.

![Imagem de capa de [SOLID #2] Princípios SOLID na prática (parte 2): ISP·DIP e por que existe a injeção de dependência](/assets/images/posts/6d8f2c8e-6a69-4df6-a991-c232fcf9500a/1.jpg)