Acoplamento (Coupling) é uma métrica que mostra o quanto um módulo de uma aplicação depende de outro. De acordo com a Wikipedia, baixo acoplamento (low coupling) é sinal de um sistema bem projetado, onde os módulos podem ser alterados sem quebrar os vizinhos. Gerenciar o coupling é uma das principais tarefas do arquiteto ao projetar aplicações mobile.
Pontos Principais
Coupling (acoplamento) é uma métrica que determina o quão fortemente um módulo ou classe está conectado a outro. Quanto mais um módulo sabe sobre a estrutura interna de outro, maior o coupling e mais difícil é alterar o sistema. Em uma arquitetura bem projetada, o coupling deve ser mínimo — os módulos interagem apenas através de interfaces estritamente definidas.
Existem dois lados do coupling: aferente (dependências de entrada — quantos módulos dependem deste) e eferente (dependências de saída — de quantos módulos este depende). Analisar essas métricas ajuda a identificar pontos críticos na arquitetura onde a alteração de um módulo afetaria muitos outros. Ferramentas como IntelliJ Dependency Analyzer e Xcode Graph visualizam essas conexões.
É importante entender que o coupling zero é impossível — os módulos precisam interagir de alguma forma, caso contrário não é um sistema, mas um conjunto de programas isolados. A tarefa do arquiteto é tornar o coupling gerenciável e transparente. O ideal: módulos interagem apenas através de interfaces e passam apenas dados simples, sem conhecer a estrutura interna um do outro. Isso é chamado de baixo acoplamento (loose coupling).
Seis tipos de coupling formam uma escala do melhor ao pior. Compreender essa escala ajuda a avaliar o código existente e escolher a direção da refatoração. A maioria dos projetos mobile tem tipos mistos de coupling, e a tarefa do arquiteto é substituir progressivamente os tipos fortes pelos fracos.
Data coupling (acoplamento de dados) — os módulos trocam apenas dados simples através de parâmetros de métodos. O módulo A chama o método do módulo B, passando primitivos ou estruturas simples, e recebe um resultado. O módulo A não sabe como B é implementado internamente. Este é o tipo mais desejável de coupling: minimiza o impacto das mudanças.
Exemplo: EmailValidator.isValid(email: String): Boolean. A classe consumidora passa uma string e recebe um Booleano, sem ideia das expressões regulares ou regras de validação dentro do validador. Alterar a lógica de validação não requer alterar o consumidor — o coupling é mínimo. Data coupling é o objetivo para todas as interfaces públicas em uma aplicação.
Stamp coupling (acoplamento por carimbo) — os módulos trocam objetos compostos, mas usam apenas parte de seus campos. O módulo A passa um objeto User para o método calculateDiscount, que usa apenas user.status. O problema: se a estrutura de User mudar (um campo obrigatório for adicionado), o módulo calculateDiscount não muda, mas o consumidor que cria o objeto User muda.
Na prática, o stamp coupling é inevitável e aceitável se o objeto passado for um modelo de dados padrão (Entity). O problema surge quando um módulo recebe um objeto inteiro apenas por um campo. Nesses casos, é melhor passar o valor específico diretamente (data coupling). A solução é analisar o uso de campos pelo lado receptor.
Control coupling — um módulo passa uma flag para outro que controla seu comportamento (calculate(useNewAlgorithm: Boolean)). Isso é pior que stamp coupling porque o módulo consumidor precisa conhecer as variantes internas de operação do módulo chamado. Solução: dividir o método em dois — calculateWithNewAlgorithm() e calculateWithLegacyAlgorithm().
External coupling — os módulos dependem de um protocolo externo, formato de dados ou API. Todos os módulos que analisam o mesmo JSON ou trabalham com o mesmo banco de dados têm external coupling. Não pode ser completamente evitado, mas pode ser isolado: criar uma camada de mapeamento entre o formato externo e os modelos internos. Common coupling — os módulos compartilham um estado global comum. Content coupling — o pior tipo, quando um módulo modifica diretamente os dados internos de outro módulo.
| Tipo de coupling | Nível | Descrição |
|---|---|---|
| Data | Melhor | Passagem de dados simples por parâmetros |
| Stamp | Aceitável | Passagem de objetos com uso parcial |
| Control | Médio | Controle de comportamento por flags |
| External | Alto | Dependência de protocolo/formato externo |
| Common | Muito alto | Compartilhamento de estado global |
| Content | Inaceitável | Modificação direta de dados internos do módulo |
A escala de coupling de data (ideal) a content (desastre) é uma ferramenta prática para revisões de código. Se você vir common ou content coupling em um projeto — esses são alvos prioritários de refatoração. Data e stamp coupling são aceitáveis e presentes em qualquer projeto, mas sua quantidade deve ser controlada.
Alto coupling transforma o desenvolvimento em um processo lento onde cada alteração requer verificar dezenas de módulos potencialmente quebrados. Isso é especialmente crítico no desenvolvimento mobile: as plataformas são atualizadas anualmente (Android API Level, iOS SDK), as bibliotecas trimestralmente e os requisitos de negócio continuamente. O baixo acoplamento é a única maneira de lidar com esse fluxo de mudanças sem regressões constantes.
Exemplo prático: um aplicativo mobile onde todas as telas importam diretamente NetworkingManager e DatabaseManager. Ao substituir o cliente HTTP de Retrofit para Ktor (Android) ou de URLSession para Alamofire (iOS), o desenvolvedor teria que modificar cada tela. Com baixo acoplamento, basta alterar uma implementação escondida atrás da interface NetworkDataSource — os consumidores não notarão a substituição.
O impacto do coupling nos testes unitários também é enorme. Uma classe com alto coupling (criação direta de dependências no construtor) não pode ser testada isoladamente — ela arrasta consigo o banco de dados, a rede e a UI. Para testar tal classe, é necessário iniciar um emulador e esperar pelos testes de integração. Uma classe com baixo coupling aceita dependências através de injeção no construtor e pode ser facilmente simulada.
// Alto coupling — a classe cria suas próprias dependências
class ProfileViewModelHigh {
private val api = RetrofitApi()
private val db = RoomDatabase.getInstance()
private val cache = MemoryCache()
}
// Baixo coupling — as dependências são passadas através do construtor
class ProfileViewModelLow(
private val api: ApiService,
private val db: DatabaseService,
private val cache: CacheService
)
No primeiro caso, ProfileViewModelHigh está fortemente ligado a implementações específicas — substituir Retrofit por Ktor requer alterar o código da ViewModel. No segundo caso, ProfileViewModelLow depende apenas de interfaces, cujas implementações são fornecidas externamente. Testar a segunda classe é trivial: passar implementações simuladas e verificar a lógica sem emulador.
Princípio da Inversão de Dependência (D no SOLID) é a base para reduzir o coupling. O princípio determina depender de abstrações, não de implementações concretas. Em vez de uma classe criar diretamente um objeto RetrofitApi, ela deve receber uma interface ApiService. Isso desloca a dependência de uma biblioteca específica para o nível de abstração, que pode ser substituído sem alterar o consumidor.
Padrão Observer (ou suas versões reativas — StateFlow, Combine Publishers) reduz o coupling entre a fonte de dados e os assinantes. O assinante não sabe de onde os dados vêm — ele simplesmente reage às mudanças. Isso desacopla o remetente e o receptor: uma nova fonte de dados pode ser adicionada sem alterar os assinantes existentes. EventBus e SharedFlow funcionam no mesmo princípio.
Padrão Bridge separa a abstração da implementação, permitindo que mudem independentemente. No desenvolvimento mobile, Bridge é usado, por exemplo, para módulos dependentes de plataforma: uma interface comum ImageLoader com diferentes implementações para iOS (Kingfisher, Nuke) e Android (Glide, Coil). O código que trabalha com ImageLoader não depende da biblioteca escolhida e pode substituí-la simplesmente alterando a implementação.
Dependency Injection (DI) é a ferramenta mais prática para reduzir o coupling no desenvolvimento mobile. Em vez de uma classe criar suas próprias dependências, um contêiner DI (Hilt, Koin, Dagger para Android; Swinject, Factory para iOS) as fornece externamente. A classe recebe as dependências através de injeção por construtor, método ou propriedade, sem conhecer as implementações concretas.
A DI documenta explicitamente as dependências da classe: basta olhar o construtor para entender com quais módulos a classe interage. Se o construtor aceita 8 parâmetros de diferentes camadas — este é um sinal de coupling excessivo que requer refatoração. Boa prática é não mais que 3-4 dependências por classe. Um número maior indica violação do Princípio da Responsabilidade Única e coupling excessivo.
A DI também simplifica os testes: para cada teste você cria uma classe com dependências simuladas, sem precisar de banco de dados ou rede real. No Flutter, a DI é implementada através de Provider, Riverpod ou GetIt. Independentemente do framework, o objetivo é um: reduzir o acoplamento entre módulos tornando as dependências explícitas e substituíveis. O uso de DI em projetos mobile tem sido o padrão de fato desde a década de 2020.
// O contêiner DI constrói o grafo de dependências
protocol AuthServiceProtocol {
func login(email: String, password: String) async throws -> User
}
final class AuthService: AuthServiceProtocol {
func login(email: String, password: String) async throws -> User {
// implementação
}
}
// ViewModel não conhece o serviço específico — apenas o protocolo
final class LoginViewModel {
private let auth: AuthServiceProtocol
init(auth: AuthServiceProtocol) {
self.auth = auth
}
}
// DI Container é o único lugar onde os tipos concretos são criados
final class DIContainer {
lazy var authService: AuthServiceProtocol = AuthService()
lazy var loginViewModel: LoginViewModel {
LoginViewModel(auth: self.authService)
}
}
Aqui, LoginViewModel depende apenas do protocolo AuthServiceProtocol, não de um AuthService específico. Substituir a implementação (por exemplo, mudar de Firebase Auth para um servidor personalizado) requer alterações apenas no DIContainer. Todos os consumidores de AuthServiceProtocol permanecem intactos — o coupling é minimizado através de abstração e DI.
Perguntas Frequentes
Coesão mede a consistência interna de um módulo, enquanto o coupling mede a interconexão externa entre módulos. Uma boa arquitetura busca alta coesão e baixo coupling. Essas métricas são inversamente proporcionais: aumentar a coesão geralmente reduz o coupling, e vice-versa.
Data e stamp são normais e estão presentes em qualquer projeto. Control coupling é aceitável em cenários limitados (por exemplo, padrão strategy). External coupling é inevitável ao trabalhar com APIs externas, mas deve ser isolado atrás de uma camada de mapeamento. Common e content coupling são sinais de problemas arquiteturais que requerem refatoração imediata.
Ferramentas de análise estática: IntelliJ IDEA Dependency Matrix, Xcode Graph, relatório de dependências do Gradle, SonarQube. Métricas: coupling aferente (Ca), coupling eferente (Ce), Instabilidade (Ce/(Ca+Ce)). Alta Instabilidade (próxima de 1) significa que o módulo é fácil de alterar e poucas coisas o referenciam — isso é bom.
Coupling extremamente baixo pode significar um número excessivo de abstrações e interfaces que complicam a navegação no código. Se uma interface separada for criada para cada classe, o programador perde tempo saltando entre arquivos. Equilíbrio: interfaces para a API externa do módulo, mas não para cada classe auxiliar interna.
Use a técnica Strangler Fig — substitua gradualmente as chamadas diretas por interfaces. Comece extraindo interfaces para as classes mais referenciadas. Em seguida, introduza um contêiner DI. Cubra o código isolado com testes de caracterização para garantir que a refatoração não altere o comportamento do sistema.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também