Peço desculpas pela longa pergunta, ela parece um pouco como um discurso retórico, mas prometo que não! Resumi minhas perguntas abaixo
No mundo MVC, as coisas são simples. O Modelo tem estado, a Visualização mostra o Modelo, e o Controlador faz coisas para / com o Modelo (basicamente), um controlador não tem estado. Para fazer coisas, o Controlador tem algumas dependências em serviços da Web, repositório e muito. Ao instanciar um controlador, você se preocupa em fornecer essas dependências, nada mais. Ao executar uma ação (método no Controller), você usa essas dependências para recuperar ou atualizar o Modelo ou chamar outro serviço de domínio. Se houver algum contexto, digamos que, como um usuário deseja ver os detalhes de um item específico, você passa o ID desse item como parâmetro para a Ação. Em nenhum lugar do Controlador há alguma referência a qualquer estado. Por enquanto, tudo bem.
Digite MVVM. Eu amo o WPF, amo a ligação de dados. Eu amo estruturas que tornam a ligação de dados ao ViewModels ainda mais fácil (usando o Caliburn Micro atm). Eu sinto que as coisas são menos simples neste mundo. Vamos fazer o exercício novamente: o modelo tem estado, a exibição mostra o ViewModel eo ViewModel faz coisas para / com o modelo (basicamente), um ViewModel faz tem estado! (para esclarecer, talvez ele delega todas as propriedades para um ou mais modelos, mas isso significa que ele deve ter uma referência ao modelo de uma forma ou de outra, que é o estado em si) Para fazercoisas que o ViewModel tem algumas dependências em serviços da web, repositório e muito. Ao instanciar um ViewModel, você se preocupa em fornecer essas dependências, mas também o estado. E isso, senhoras e senhores, me irrita sem fim.
Sempre que você precisar instanciar a ProductDetailsViewModel
partir de ProductSearchViewModel
(do qual você chamou o ProductSearchWebService
que por sua vez retornou IEnumerable<ProductDTO>
, todo mundo ainda está comigo?), Você pode fazer uma destas coisas:
- chamada
new ProductDetailsViewModel(productDTO, _shoppingCartWebService /* dependcy */);
, isso é ruim, imagine mais 3 dependências, isso significa que também éProductSearchViewModel
necessário assumir essas dependências. Mudar o construtor também é doloroso. - Por exemplo
_myInjectedProductDetailsViewModelFactory.Create().Initialize(productDTO);
, a fábrica é apenas um Func, eles são facilmente gerados pela maioria das estruturas de IoC. Eu acho que isso é ruim porque os métodos Init são uma abstração com vazamento. Você também não pode usar a palavra-chave readonly para campos definidos no método Init. Tenho certeza de que existem mais algumas razões. - call
_myInjectedProductDetailsViewModelAbstractFactory.Create(productDTO);
Então ... esse é o padrão (fábrica abstrata) geralmente recomendado para esse tipo de problema. Eu pensei que era genial, pois satisfaz meu desejo de digitar estática, até que comecei a usá-lo. A quantidade de código clichê é, eu acho demais (você sabe, além dos nomes de variáveis ridículos que eu uso). Para cada ViewModel que precisa de parâmetros de tempo de execução, você obtém dois arquivos extras (interface e implementação de fábrica) e precisa digitar as dependências que não são de tempo de execução, como 4 vezes extras. E toda vez que as dependências mudam, você também muda na fábrica. Parece que eu nem uso mais um contêiner de DI. (Eu acho que o Castelo Windsor tem algum tipo de solução para isso [com suas próprias desvantagens, me corrija se eu estiver errado]). - faça algo com tipos ou dicionário anônimos. Eu gosto da minha digitação estática.
Então sim. A mistura de estado e comportamento dessa maneira cria um problema que não existe no MVC. E sinto que atualmente não há uma solução realmente adequada para esse problema. Agora eu gostaria de observar algumas coisas:
- As pessoas realmente usam MVVM. Portanto, eles não se preocupam com tudo isso, ou têm alguma outra solução brilhante.
- Não encontrei um exemplo detalhado de MVVM com WPF. Por exemplo, o projeto de amostra NDDD me ajudou imensamente a entender alguns conceitos de DDD. Eu realmente gostaria que alguém pudesse me apontar na direção de algo semelhante para o MVVM / WPF.
- Talvez eu esteja fazendo MVVM errado e eu deva virar meu design de cabeça para baixo. Talvez eu não devesse ter esse problema. Bem, eu sei que outras pessoas fizeram a mesma pergunta, então acho que não sou a única.
Resumir
- Estou correto ao concluir que ter o ViewModel sendo um ponto de integração para o estado e o comportamento é o motivo de algumas dificuldades com o padrão MVVM como um todo?
- O uso do padrão abstrato de fábrica é a única / melhor maneira de instanciar um ViewModel de maneira estaticamente tipada?
- Existe algo como uma implementação de referência detalhada disponível?
- Ter um monte de ViewModels com estado / comportamento é um cheiro de design?
Respostas:
A questão das dependências ao iniciar um novo modelo de visualização pode ser tratada com o IOC.
Ao instalar o contêiner ...
Quando você precisa do seu modelo de visualização:
Ao utilizar uma estrutura como o caliburn micro , geralmente existe alguma forma de contêiner do COI.
fonte
Trabalho diariamente com o ASP.NET MVC e trabalho em um WPF há mais de um ano, e é assim que vejo:
MVC
O controlador deve orquestrar ações (busque isso, adicione aquilo).
A visualização é responsável por exibir o modelo.
O modelo normalmente abrange dados (por exemplo, UserId, FirstName) e também o estado (por exemplo, títulos) e geralmente é específico para exibição.
MVVM
O modelo normalmente contém apenas dados (por exemplo, UserId, FirstName) e geralmente é transmitido
O modelo de visualização abrange o comportamento da visualização (métodos), seus dados (modelo) e interações (comandos) - semelhantes ao padrão MVP ativo em que o apresentador está ciente do modelo. O modelo de vista é específico da vista (1 vista = 1 modelo de vista).
A visualização é responsável por exibir dados e ligação de dados ao modelo de visualização. Quando uma vista é criada, geralmente seu modelo de vista associado é criado com ela.
O que você deve se lembrar é que o padrão de apresentação MVVM é específico ao WPF / Silverlight devido à sua natureza de ligação de dados.
A visualização geralmente sabe com qual modelo de visualização está associado (ou uma abstração de uma).
Eu aconselho que você trate o modelo de exibição como um singleton, mesmo que seja instanciado por exibição. Em outras palavras, você deve ser capaz de criá-lo via DI através de um contêiner do IOC e chamar métodos apropriados para dizer; carregar seu modelo com base em parâmetros. Algo assim:
Como exemplo, neste caso, você não criaria um modelo de visualização específico para o usuário que está sendo atualizado. Em vez disso, o modelo conteria dados específicos do usuário que são carregados por meio de alguma chamada no modelo de visualização.
fonte
Resposta curta para suas perguntas:
A versão longa:
Estamos enfrentando o mesmo problema e encontramos algumas coisas que podem ajudá-lo. Embora eu não conheça a solução "mágica", essas coisas estão aliviando um pouco a dor.
Implemente modelos vinculáveis de DTOs para rastreamento e validação de alterações. Esses modelos de "dados" não devem depender de serviços e não vêm do contêiner. Eles podem ser apenas "novos", distribuídos e podem até derivar do DTO. Bottomline é implementar um modelo específico para sua aplicação (como MVC).
Desacoplar seus ViewModels. O Caliburn facilita o acoplamento dos ViewModels. Até o sugere através do seu modelo Screen / Conductor. Mas esse acoplamento torna os ViewModels difíceis de testar, cria muitas dependências e o mais importante: impõe o ônus de gerenciar o ciclo de vida do ViewModel nos seus ViewModels. Uma maneira de dissociá-los é usar algo como um serviço de navegação ou um controlador ViewModel. Por exemplo
interface pública IShowViewModels {void Show (objeto inlineArgumentsAsAnonymousType, string regionId); }
Melhor ainda é fazer isso de alguma forma. Mas o importante é não lidar com o ciclo de vida do ViewModel de outros ViewModels. No MVC, os controladores não dependem um do outro e no MVVM ViewModels não devem depender um do outro. Integre-os de outras maneiras.
INeedData<T1,T2,...>
e impor parâmetros de criação com segurança de tipo, não vale a pena. Também não vale a pena criar fábricas para cada tipo de ViewModel. A maioria dos contêineres IoC fornece soluções para isso. Você obterá erros no tempo de execução, mas o desacoplamento e a testabilidade da unidade valem a pena. Você ainda faz algum tipo de teste de integração e esses erros são detectados facilmente.fonte
A maneira como costumo fazer isso (usando o PRISM) é que cada assembly contém um módulo de inicialização de contêiner, no qual todas as interfaces e instâncias são registradas na inicialização.
E, dadas as classes de exemplo, seriam implementadas dessa maneira, com o contêiner sendo passado por todo o caminho. Dessa forma, quaisquer novas dependências podem ser adicionadas facilmente, pois você já tem acesso ao contêiner.
É bastante comum ter uma classe ViewModelBase, da qual todos os seus modelos de exibição são derivados, que contém uma referência ao contêiner. Contanto que você adquira o hábito de resolver todos os modelos de exibição em vez
new()'ing
deles, isso deverá tornar toda a resolução de dependências muito mais simples.fonte
Às vezes, é bom ir para a definição mais simples, em vez de um exemplo completo: http://en.wikipedia.org/wiki/Model_View_ViewModel talvez a leitura do exemplo ZK Java seja mais esclarecedora que a do C #.
Outras vezes, ouça seu instinto ...
Seus modelos são objetos por mapeamentos de tabela? Talvez um ORM ajude a mapear objetos do domínio enquanto lida com os negócios ou atualiza várias tabelas.
fonte