Comparativo de Proliferação de Classes por Camada
Para ilustrar o impacto das escolhas arquitetônicas na manutenção do projeto, imagine um requisito que exige três componentes visuais principais: TelaLista, TelaDetalhes e TelaPré-visualização. Cada um deve manipular seis domínios de negócio (três para notas/trilhas e três para usuários/analytics). A contagem estimada de arquivos/classe necessários para suportar essa camada é:
| Padrão Adotado | Entidades Geradas | Total Estimado |
|---|---|---|
| MVC | 3 Views + 3 Controladores + 2 Repositórios | 8 |
| MVP | 3 Views + 3 Presenters + 3 Models + 1 Contrato | 10 |
| Clean | 3 Views + 3 ViewModel + 18 UseCases + 3 Repositórios | 27 |
| AAC (MVVM/LiveData) | 3 Views + 3 ViewModel + 3 Repositórios | 9 |
Arquitetura MVC: Dependências Mútuas e Falta de Interfaces
No modelo clássico, a visualização possui acesso direto ao serviço de dados. Isso elimina qualquer barreira de proteção entre a camada de apresentação e o domínio, tornando difícil atribuir responsabilidades distintas entre desenvolvedores ou substituir implementações locais sem quebrar contratos não explícitos.
public class ControleListaTarefa extends BaseComponenteUI {
private ExecutorServico executor = Executores.newSingleThreadExecutor();
private AtualizadorUI gerenciador = new Handler(UI);
private RepositorioBase dados;
public void atualizarGrade() {
executor.execute(() -> {
List<EntidadeTrabalho> lote = dados.buscarTodos();
gerenciador.post(() ->
AdaptadorGrade.atualizarDados(lote)
);
});
}
}
Arquitetura MVP: Contratos Explícitos e Subdomínio do Intermediário
O MVP itnroduz camadas de abstração para permitir a troca isolada de componentes. Interfaces são definidas para que a View never invoque métodos diretos do Model, e vice-versa. Contudo, a responsabilidade de Orquestrar fluxo de execução acaba concentrada no Intermediador, que frequentemente misturas lógica de negócios com manipulação de estado visual.
public interface ContratoTralha {
interface InterfaceTela {
void renderizarMensagem(String texto);
void exibirGrade(List<EntidadeTrabalho> items);
}
interface InterfaceNegocio {
void solicitarColecao(String filtro);
void aplicarAlteracoes(EntidadeTrabalho item);
}
}
public class IntermediadorPrincipal implements ContratoTralha.InterfaceNegocio {
private ContratoTralha.InterfaceTela visualizacao;
private ContratoTralha.ModeloRepo repositorio;
@Override
public void solicitarColecao(String criterio) {
ThreadPoolExec.submit(() -> {
List<EntidadeTrabalho> resultado = repositorio.obter(criterio);
visualizarizacao.exibirGrade(resultado);
});
}
}
Esse design assume erroneamente que a lógica de interface é estática enquanto os dados mudam constantemente. Na prática, alterações visuais demandam ajustes constantes no mediador, gerando conflitos de merge e dificultando a autonomia do desenvolvedor de UI.
Arquitetura Limpa (Clean): Fluxo Unidirecional e Verbosidade Excessiva
A Clean impõe uma hierarquia rígida onde o domínio dita as regras e a apresentação apenas consome estados reativos. O controle é delegada aos ViewModel que atuam como intermediários passivos, enviando requisições a instâncias dedicadas de UseCase. Apesar de garantir testabilidade isolada, cada caso de uso exige definições manuais de entrada e saída, gerando redundância massiva conforme a tela escala.
public class PainelViewModelEstendido {
MutableLiveData<ListaWrapper> estadoLista = new MutableLiveData<>();
GestorUsoCasos gestor = new GestorUsoCasos();
public void buscarItens(String tipo) {
var requisicao = new ParametrosConsulta(tipo);
gestor.executar(new RecuperarDadosUseCase(), requisicao, novo RetornoCallback() {
@Override public void suceso(DadosRetorno resposta) {
estadoLista.setValue(resposta.conteudo);
}
});
}
}
public class RecuperarDadosUseCase extends CasoUsoGenérico<ParametrosConsulta, DadosRetorno> {
private RepositorioInterno cache;
protected void executarEmFundo(ParametrosConsulta params) {
var lista = cache.obterHistorico(params.filtro);
invocarSucesso(new DadosRetorno(lista));
}
}
A granularidade extrema obriga o criador a definir wrappers de parâmetros repetitivos para quase cada endpoint, elevando drasticamente a curva de manutenção.
AAC e Observadores Nativos: Simplificação e Empurrão de Lógica
A abordagem nativa (AAC) abandona intermediadores pesados em favor de fluxos de dados observáveis diretamente vinculados ao ciclo de vida do componente. O código exposto abaixo demonstra como a View se registra para receber transformações automáticas, reduzindo boilerplate mas deslocando toda a complexidade de transformação para o módulo de repositórios.
public class GerenciadorConteudo extends ComponenteActivity {
private PainelViewModel viewModel = new ViewModelProvider(this).get(PainelViewModel.class);
@Override
protected void configurarFluxo() {
viewModel.estadoAtual.observe(viewLifecycleOwner, itemAtual -> {
if (itemAtual != null) {
RenderizadorUI.aplicar(itemAtual);
}
});
}
}
Essa simbiose aproxima-se de modelos cliente-servidor modernos: o frontend solicita, o backend processa e retorna um payload pronto. A vantagem é produtividade, porém a desvantagem é a perda de flexibilidade quando múltiplos consumidores precisam tratar o mesmo evento de formas distintas.
Abordagem Baseada em Mensageria Centralizada
Para mitigar o acoplamento residual presente nos modelos anteriores, opta-se por um padrão orientado a eventos através de uma barramenta central (MediadorMensagens). Neste modelo, a interface nunca conhece a origem dos dados; ela publica intencionalidades na canal comum. Dispositivos registram no canal qual intenção desejam consumir e enviam apenas o resultado bruto de volta.
- Desacoplamento Radical: A camada visual não importa quem atende à chamada, apenas que alguém respondeu ao ID da ação.
- Compartilhamento de Handlers: Distintos módulos de UI podem subscriveer-se ao mesmo manipulador de domínio, evitando duplicação de código.
- Resiliência Operacional: Erros são encapsulados em payloads de erro padronizados, permitindo que a interface reaja consistentemente sem dependências cirúrgicas.
A adoção desse mecanismo transforma a comunicação interna em um sistema distribuído leve, onde a fronteira entre UX e Regra de Negócio torna-se estanque e previsível.