Evolução dos Padrões Arquiteturais no Android: MVC, MVP, Clean e AAC Sob a Perspectiva de Desacoplamento

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.

Tags: android mvc-mvp-clean-arch event-driven-ui mediator-pattern separacao-de-responsabilidades

Publicado em 10-20 16:36