O MVVM é um band-aid para camadas de ligação de dados mal projetadas. Em particular, ele tem sido muito utilizado no mundo WPF / silverlight / WP7 devido a limitações na ligação de dados no WPF / XAML.
A partir de agora, vou assumir que estamos falando sobre WPF / XAML, pois isso tornará as coisas mais claras. Vamos analisar algumas das deficiências que o MVVM se propõe a resolver no WPF / XAML.
Formato dos dados x formato da interface do usuário
A 'VM' no MVVM cria um conjunto de objetos definidos em C # que são mapeados para um conjunto de objetos de apresentação definido em XAML. Esses objetos C # geralmente são conectados ao XAML por meio de propriedades DataContext nos objetos de apresentação.
Como resultado, o gráfico de objeto viewmodel precisa mapear no gráfico de objeto de apresentação do aplicativo. Isso não quer dizer que o mapeamento precise ser individual, mas se um controle de lista estiver contido em um controle de janela, deve haver uma maneira de passar do objeto DataContext da janela para um objeto que descreva os dados dessa lista.
O gráfico de objeto do viewmodel desacopla o gráfico do objeto de modelo do gráfico da interface do usuário com êxito, mas às custas de uma camada adicional do viewmodel que deve ser construída e mantida.
Se eu quiser mover alguns dados da tela A para a tela B, preciso mexer nos modelos de exibição. Na mente de um profissional, essa é uma alteração na interface do usuário. Ele deve ter lugar exclusivamente no mundo do XAML. Infelizmente, isso raramente acontece. Pior ainda, dependendo de como os modelos de exibição estão estruturados e de quão ativamente os dados são alterados, pode ser necessário um redirecionamento de dados para realizar essa alteração.
Como solucionar a ligação de dados não expressiva
As ligações WPF / XAML são insuficientemente expressivas. Você basicamente fornece uma maneira de chegar a um objeto, um caminho de propriedade para percorrer e vincular conversores para adaptar o valor da propriedade de dados ao que o objeto de apresentação exige.
Se você precisar vincular uma propriedade em C # a algo mais complexo que isso, estará basicamente sem sorte. Eu nunca vi um aplicativo WPF sem um conversor de ligação que se tornou verdadeiro / falso em Visível / Reduzido. Muitos aplicativos WPF também tendem a ter algo chamado NegatingVisibilityConverter ou similar que inverte a polaridade. Isso deve estar disparando alarmes.
O MVVM fornece diretrizes para estruturar seu código C # que pode ser usado para suavizar essa limitação. Você pode expor uma propriedade no seu viewmodel chamada SomeButtonVisibility e apenas vinculá-la à visibilidade desse botão. Seu XAML é legal e bonito agora ... mas você se tornou um funcionário - agora você precisa expor + atualizar as ligações em dois lugares (a interface do usuário e o código em C #) quando a interface do usuário evolui. Se você precisar que o mesmo botão esteja em outra tela, precisará expor uma propriedade semelhante em um modelo de exibição que essa tela possa acessar. Pior, não posso apenas olhar para o XAML e ver quando o botão ficará mais visível. Assim que as ligações se tornam um pouco triviais, tenho que fazer um trabalho de detetive no código C #.
O acesso aos dados tem escopo agressivo
Como os dados geralmente entram na interface do usuário por meio das propriedades do DataContext, é difícil representar dados globais ou de sessão de maneira consistente em todo o aplicativo.
A idéia do "usuário conectado no momento" é um ótimo exemplo - isso geralmente é algo verdadeiramente global em uma instância do seu aplicativo. No WPF / XAML, é muito difícil garantir o acesso global ao usuário atual de maneira consistente.
O que eu gostaria de fazer é usar a palavra "CurrentUser" em ligações de dados livremente para se referir ao usuário conectado no momento. Em vez disso, tenho que garantir que todos os DataContext me permitam acessar o objeto de usuário atual. O MVVM pode acomodar isso, mas os modelos de exibição serão uma bagunça, pois todos eles precisam fornecer acesso a esses dados globais.
Um exemplo em que o MVVM cai
Digamos que temos uma lista de usuários. Ao lado de cada usuário, queremos exibir um botão "excluir usuário", mas apenas se o usuário conectado no momento for um administrador. Além disso, os usuários não têm permissão para se excluir.
Os objetos do modelo não devem saber sobre o usuário conectado no momento - eles apenas representam registros do usuário no banco de dados, mas de alguma forma o usuário conectado no momento precisa ser exposto a ligações de dados nas linhas da lista. O MVVM determina que devemos criar um objeto viewmodel para cada linha da lista que compõe o usuário conectado no momento com o usuário representado por essa linha da lista e expor uma propriedade chamada "DeleteButtonVisibility" ou "CanDelete" nesse objeto viewmodel (dependendo de seus sentimentos) sobre conversores de ligação).
Esse objeto parecerá muito com um objeto Usuário da maioria das outras maneiras - talvez seja necessário refletir todas as propriedades do objeto do modelo de usuário e encaminhar atualizações para esses dados à medida que forem alterados. Isso é realmente nojento - mais uma vez, o MVVM o transforma em funcionário, forçando-o a manter esse objeto semelhante ao usuário.
Considere - você provavelmente também precisa representar as propriedades do usuário em um banco de dados, modelo e exibição. Se você tem uma API entre você e seu banco de dados, é pior - eles estão representados no banco de dados, no servidor da API, no cliente da API, no modelo e na visualização. Eu realmente hesitaria em adotar um padrão de design que adicionasse outra camada que precisa ser tocada toda vez que uma propriedade for adicionada ou alterada.
Pior ainda, essa camada é dimensionada com a complexidade da sua interface do usuário, não com a complexidade do seu modelo de dados. Muitas vezes, os mesmos dados são representados em muitos lugares e na sua interface do usuário - isso não apenas adiciona uma camada, mas também uma camada com muita área de superfície extra!
Como as coisas poderiam ter sido
No caso descrito acima, eu gostaria de dizer:
<Button Visibility="{CurrentUser.IsAdmin && CurrentUser.Id != Id}" ... />
O CurrentUser seria exposto globalmente a todos os XAML no meu aplicativo. Id se referiria a uma propriedade no DataContext para a minha linha da lista. A visibilidade converteria de booleano automaticamente. Quaisquer atualizações no Id, CurrentUser.IsAdmin, CurrentUser ou CurrentUser.Id acionariam uma atualização na visibilidade desse botão. Mole-mole.
Em vez disso, o WPF / XAML força seus usuários a criar uma bagunça completa. Até onde eu sei, alguns blogueiros criativos colocaram um nome nessa bagunça e esse nome era MVVM. Não se deixe enganar - ele não pertence à mesma classe que os padrões de design do GoF. Este é um truque feio para solucionar um feio sistema de ligação de dados.
(Essa abordagem às vezes é chamada de "Programação Reativa Funcional", caso você esteja procurando mais leituras).
Em conclusão
Se você deve trabalhar no WPF / XAML, ainda não recomendo o MVVM.
Você deseja que seu código seja estruturado como o exemplo "como as coisas poderiam ter sido" acima, o modelo exposto diretamente para visualização, com expressões complexas de ligação de dados + coerções flexíveis de valor. É muito melhor - mais legível, mais gravável e mais sustentável.
O MVVM diz para você estruturar seu código de maneira mais detalhada e menos sustentável.
Em vez do MVVM, crie algumas coisas para ajudá-lo a aproximar a boa experiência: Desenvolva uma convenção para expor o estado global à sua interface do usuário de forma consistente. Crie você mesmo algumas ferramentas de conversores de ligação, MultiBinding, etc., que permitem expressar expressões de ligação mais complexas. Crie uma biblioteca de conversores de ligação para ajudar a tornar os casos de coerção menos dolorosos.
Ainda melhor - substitua XAML por algo mais expressivo. XAML é um formato XML muito simples para instanciar objetos C # - não seria difícil criar uma variante mais expressiva.
Minha outra recomendação: não use kits de ferramentas que forçam esses tipos de comprometimentos. Eles prejudicarão a qualidade do seu produto final, empurrando você para uma porcaria como o MVVM, em vez de se concentrar no domínio do problema.