Android Architecture Patterns Parte 1: Model-View-Controller
Um ano atrás, quando a maioria da equipe Android atual começou a trabalhar na atualização, o aplicativo estava longe de ser o aplicativo robusto e estável que queríamos que fosse. Tentamos entender por que nosso código estava em tão mau estado e encontramos dois culpados principais: a mudança contínua da IU e a falta de uma arquitetura que oferecesse a flexibilidade de que precisávamos. O aplicativo já estava em sua quarta reformulação em seis meses. O design pattern escolhido parecia ser Model-View-Controller, mas então já era um “mutante”, longe do que deveria ser.
Vamos descobrir juntos o que é o padrão Model-View-Controller; como ele foi aplicado no Android ao longo dos anos; como deve ser aplicado para maximizar a testabilidade; e algumas de suas vantagens e desvantagens.
Conteudo
O padrão Model-View-Controller
Em um mundo onde a lógica da interface do usuário tende a mudar com mais frequência do que a lógica do negócio, os desenvolvedores de desktop e da Web precisavam de uma maneira de separar a funcionalidade da interface do usuário. O padrão MVC foi a solução.
- Model – a camada de dados, responsável por gerenciar a lógica de negócios e lidar com a rede ou API de banco de dados.
- View – a camada de IU – uma visualização dos dados do modelo.
- Controller – a camada lógica, é notificado sobre o comportamento do usuário e atualiza o modelo conforme necessário.
Então, isso significa que tanto o Controlador quanto a Visualização dependem do Modelo: o Controlador para atualizar os dados, a Visualização para obter os dados. Mas, o mais importante para os desenvolvedores de desktop e da Web naquela época: o modelo era separado e podia ser testado independentemente da IU. Diversas variantes do MVC apareceram. Os mais conhecidos estão relacionados ao fato de o modelo ser passivo ou notificar ativamente que mudou. Aqui estão mais detalhes:
Modelo Passivo
Na versão do Modelo Passivo, o Controlador é a única classe que manipula o Modelo. Com base nas ações do usuário, o Controlador deve modificar o Modelo. Depois que o modelo for atualizado, o controlador notificará a visualização de que ele também precisa ser atualizado. Nesse ponto, a View irá solicitar os dados do Model.
Modelo Ativo
Para os casos em que o Controller não é a única classe que modifica o Model, o Model precisa de uma forma de notificar a View, e outras classes, sobre atualizações. Isso é feito com a ajuda do padrão Observer. O modelo contém uma coleção de observadores interessados em atualizações. A View implementa a interface do observador e se registra como um observador no Model.
Sempre que o modelo é atualizado, ele também itera através da coleção de observadores e chama o método de atualização. A implementação deste método na View irá então acionar a solicitação dos dados mais recentes do Model.
Model-View-Controller no Android
Por volta de 2011, quando o Android começou a se tornar cada vez mais popular, as questões de arquitetura surgiram naturalmente. Como o MVC era um dos padrões de interface do usuário mais populares na época, os desenvolvedores também tentaram aplicá-lo ao Android.
Se você pesquisar no StackOverflow por questões como “Como aplicar MVC no Android”, uma das respostas mais populares afirmou que, no Android, uma atividade é tanto a visualização quanto o controlador. Olhando para trás, isso parece quase loucura! Mas, nesse ponto, a ênfase principal era tornar o Modelo testável e, geralmente, a escolha de implementação para a Visualização e o Controlador dependia da plataforma.
Como o MVC deve ser aplicado no Android
Hoje em dia, a questão de como aplicar os padrões MVC tem uma resposta mais fácil de encontrar. As atividades, fragmentos e visualizações devem ser as visualizações no mundo MVC. Os controladores devem ser classes separadas que não estendem ou usam nenhuma classe Android, e o mesmo para os modelos.
Um problema surge ao conectar o Controlador ao View, já que o Controller precisa dizer ao View para atualizar. Na arquitetura passiva do Modelo MVC, o Controlador precisa conter uma referência para a Visualização. A maneira mais fácil de fazer isso, enquanto se concentra em testes, é ter uma interface BaseView, que a Activity/Fragment/View estenderia. Portanto, o Controlador teria uma referência ao BaseView.
Vantagens
O padrão Model-View-Controller suporta fortemente a separação de interesses. Essa vantagem não apenas aumenta a testabilidade do código, mas também o torna mais fácil de estender, permitindo uma implementação bastante fácil de novos recursos.
As classes Model não têm nenhuma referência às classes Android e, portanto, são diretas para teste de unidade. O Controlador não estende ou implementa nenhuma classe Android e deve ter uma referência a uma classe de interface da Visualização. Desta forma, o teste de unidade do Controlador também é possível.
Se as Views respeitarem o princípio de responsabilidade única, então sua função é apenas atualizar o Controller para cada evento do usuário e apenas exibir os dados do Modelo, sem implementar nenhuma lógica de negócios. Nesse caso, os testes de IU devem ser suficientes para cobrir as funcionalidades do View.
Desvantagens
A visualização depende do controlador e do modelo
A dependência da View no Model começa a ser uma desvantagem em Views complexas. Para minimizar a lógica na Visualização, o Modelo deve ser capaz de fornecer métodos testáveis para cada elemento que for exibido. Em uma implementação de Model ativa, isso aumenta exponencialmente o número de classes e métodos, visto que seriam necessários Observadores para cada tipo de dados.
Dado que a View depende tanto do Controller quanto do Model, as mudanças na lógica da IU podem exigir atualizações em várias classes, diminuindo a flexibilidade do padrão.
Quem lida com a lógica da interface do usuário?
De acordo com o padrão MVC, o Controlador atualiza o Modelo e a Visualização obtém os dados a serem exibidos no Modelo. Mas quem decide como exibir os dados? É o modelo ou a visão? Considere o seguinte exemplo: temos um usuário, com nome e sobrenome. Na Visualização, precisamos exibir o nome do usuário como “Sobrenome, Nome” (por exemplo, “Doe, João”).
Se a função do modelo é apenas fornecer os dados “brutos”, isso significa que o código na visualização seria:
String firstName = userModel.getFirstName(); String lastName = userModel.getLastName(); nameTextView.setText(lastName + ", " + firstName)
Portanto, isso significa que seria responsabilidade do View lidar com a lógica da IU. Mas isso torna a lógica da IU impossível para o teste de unidade.
A outra abordagem é fazer com que o Modelo exponha apenas os dados que precisam ser exibidos, ocultando qualquer lógica de negócios da Visualização. Mas então, acabamos com modelos que lidam com a lógica de negócios e da interface do usuário. Seria testável por unidade, mas então o Model acaba sendo implicitamente dependente da View.
String name = userModel.getDisplayName(); nameTextView.setText(name);
Conclusão
Nos primeiros dias do Android, o padrão Model-View-Controller parecia ter confundido muitos desenvolvedores e levado a um código que era difícil, senão impossível, de teste de unidade.
A dependência da View do Model e ter lógica na View conduziu nossa base de código a um estado do qual era impossível recuperar sem refatorar completamente o aplicativo. Qual foi a nova abordagem em arquitetura e por quê?