Pensamentos de implementação do Model-View-Presenter

34

Estou tentando entender bem como implementar uma boa dissociação entre uma interface do usuário e o modelo, mas estou tendo problemas para descobrir exatamente onde dividir as linhas.

Eu estive analisando o Model-View-Presenter, mas não sei exatamente como proceder para implementá-lo. Por exemplo, minha Visualização possui várias caixas de diálogo.

  • Deve haver uma classe View com instâncias de cada uma das caixas de diálogo? Nesse caso, como os diálogos devem interagir com o Apresentador? ie se um diálogo individual precisar solicitar dados do modelo por meio do apresentador, como o diálogo deve obter uma referência ao apresentador? Através de uma referência à vista que lhe foi dada durante a construção?
  • Eu estava pensando que talvez a visão devesse ser uma classe estática? Em seguida, as caixas de diálogo GetView e o Presenter de lá ...
  • Eu estava pensando em configurá-lo para o Presenter com propriedade do View and Model (em oposição ao View com o Presenter e o Presenter com Model) e o Presenter registrando retornos de chamada para eventos no View, mas isso faz parecer muito mais acoplados (ou pelo menos o idioma dependia).

Eu estou tentando:

  1. faça isso o mais dissociado possível
  2. idealmente, é possível associar o Presenter / Model às exibições de outros idiomas (eu não fiz muitas coisas inter-idiomas, mas sei que é possível, principalmente quanto mais void(void)posso me apegar, pelo menos um aplicativo C # com um Biblioteca C ++ ...
  3. mantenha o código limpo e simples

Então .. alguma sugestão de como as interações devem ser tratadas?

trycatch
fonte
Você já olhou para este artigo ?: en.wikipedia.org/wiki/Model-view-presenter
Bernard
1
Eu tenho .. eu achei um rápido bit e alto nível, eu estou olhando para entender melhor como lidar com várias caixas de diálogo em um grande projeto com tão pouco acoplamento possível ..
trycatch

Respostas:

37

Bem-vindo a uma ladeira escorregadia. Você já percebeu que existe uma variação infinita de todas as interações de visualização de modelo. MVC, MVP (Taligent, Dolphin, Passive View), MVVM apenas para citar alguns.

O padrão do Model View Presenter, como a maioria dos padrões de arquitetura, está aberto a muita variedade e experimentação. A única coisa que todas as variações têm em comum é o papel do apresentador como um "intermediário" entre a visualização e o modelo. Os dois mais comuns são a visão passiva e o apresentador / controlador de supervisão - [ Fowler ]. O Passive View trata a interface do usuário como uma interface muito superficial entre o usuário e o apresentador. Ele contém muito pouca ou nenhuma lógica, delegando tanta responsabilidade a um apresentador. Apresentador / Controlador Supervisortenta tirar proveito da ligação de dados incorporada em muitas estruturas de interface do usuário. A interface do usuário lida com a sincronização de dados, mas o apresentador / controlador adota uma lógica mais complexa. Nos dois casos, o modelo, a visualização e o apresentador formam uma tríade

Há muitas maneiras de fazer isso. É muito comum ver isso tratado, tratando cada caixa de diálogo / formulário como uma visão diferente. Muitas vezes, existe uma relação 1: 1 entre visualizações e apresentadores. Esta não é uma regra difícil e rápida. É bastante comum que um apresentador lide com várias visualizações relacionadas ou vice-versa. Tudo depende da complexidade da visualização e da complexidade da lógica de negócios.

Quanto à maneira como as visualizações e os apresentadores obtêm uma referência um para o outro, isso às vezes é chamado de fiação . Você tem três opções:

A exibição contém uma referência ao apresentador.
Um formulário ou caixa de diálogo implementa uma exibição. O formulário possui manipuladores de eventos que delgate para um apresentador usando chamadas de função diretas:

MyForm.SomeEvent(Sender)
{
  Presenter.DoSomething(Sender.Data);
}

Como o apresentador não tem uma referência à visualização, ela deve enviar dados como argumentos. O apresentador pode se comunicar de volta à exibição usando funções de eventos / retorno de chamada que a exibição deve escutar.

O Presenter mantém uma referência para exibição.
No cenário, a exibição expõe propriedades para os dados exibidos para o usuário. O apresentador escuta eventos e manipula as propriedades na exibição:

Presenter.SomeEvent(Sender)
{
  DomainObject.DoSomething(View.SomeProperty);
  View.SomeOtherProperty = DomainObject.SomeData;
}

Ambos mantêm uma referência um ao outro, formando uma dependência circular.
Este cenário é realmente mais fácil de trabalhar do que os outros. A exibição responde aos eventos chamando métodos no apresentador. O apresentador lê / modifica dados da exibição por meio de propriedades expostas.

View.SomeEvent(Sender)
{
  Presenter.DoSomething();
}

Presenter.DoSomething()
{
  View.SomeProperty = DomainObject.Calc(View.SomeProperty);
}

Há outros problemas a serem considerados nos padrões do MVP. Ordem de criação, vida útil do objeto, onde a fiação ocorre, comunicação entre as tríades MVP, mas essa resposta já foi longa o suficiente.

Kenneth Cochran
fonte
1
Isso é definitivamente útil. A comunicação entre as tríades e a vida é onde estou tendo problemas agora, agora que estou entendendo um pouco disso.
trycatch
8

Como todos disseram, existem dezenas de opiniões e nenhuma delas está certa ou errada. Sem entrar na miríade de padrões e focando apenas no MVP, aqui estão algumas sugestões sobre implementação.

Mantenha-os separados. A visão deve implementar uma interface que forma o vínculo entre a visão e o apresentador. A exibição cria um apresentador e se injeta no apresentador e expõe os métodos oferecidos para o apresentador interagir com a exibição. A visualização é responsável por implementar esses métodos ou propriedades da maneira que desejar. Geralmente, você tem uma visualização: um apresentador, mas em alguns casos você pode ter muitas visualizações: um apresentador (web, wpf, etc.). A chave aqui é que o apresentador não sabe nada sobre implementações da interface do usuário e apenas interage com a exibição pela interface.

Aqui está um exemplo. Primeiro, temos uma classe de exibição com um método simples para exibir uma mensagem para o usuário:

interface IView
{
  public void InformUser(string message);
}

Agora aqui está o apresentador. Observe que o apresentador recebe uma IView em seu construtor.

class Presenter
{
  private IView _view;
  public Presenter(IView view)
  {
    _view = view;
  }
}

Agora, aqui está a interface do usuário real. Pode ser uma janela, uma caixa de diálogo, uma página da web etc. Não importa. Observe que o construtor da exibição criará o apresentador injetando-se nele.

class View : IView
{
  private Presenter _presenter;

  public View()
  {
    _presenter = new Presenter(this);
  }

  public void InformUser(string message)
  {
    MessageBox.Show(message);
  }
}

O apresentador não se importa com a maneira como a exibição implementa o método que ele faz. Pelo que o apresentador sabe, ele pode estar gravando em um arquivo de log e nem mesmo mostrá-lo ao usuário.

De qualquer forma, o apresentador trabalha com o modelo no back-end e, em algum momento, deseja informar o usuário sobre o que está acontecendo. Portanto, agora temos um método em algum lugar no apresentador que chama para a mensagem InformUser dos modos de exibição.

class Presenter
{
  public void DoSomething()
  {
    _view.InformUser("Starting model processing...");
  }
}

É aqui que você obtém sua dissociação. O apresentador mantém apenas uma referência a uma implementação do IView e realmente não se importa como ele é implementado.

Essa também é uma implementação pobre, pois você tem uma referência ao Presenter na visualização e os objetos são definidos por meio de construtores. Em uma solução mais robusta, você provavelmente desejaria examinar contêineres de inversão de controle (IoC), como Windsor, Ninject etc., que resolveriam a implementação do IView para você em tempo de execução sob demanda e, assim, tornando-o ainda mais dissociado.

Bil Simser
fonte
4

Eu acho que é importante lembrar que o Controller / Presenter é onde a ação realmente ocorre. O acoplamento no controlador é inevitável por causa da necessidade.

O ponto principal do Controller é que, se você fizer uma alteração na View, o Model não precisará mudar e vice-versa (se o Model alterar a View também não precisa) porque o Controller é o que traduz o Modele na Vista e volte novamente. Mas o Controlador mudará quando as alterações de Modelo ou de Visão o fizerem, porque você precisa traduzir efetivamente dentro do Controlador como o Modelo é Visto como obter as alterações feitas na Visualização de volta ao Modo.

O melhor exemplo que posso dar é que, ao escrever um aplicativo MVC, sou capaz de não apenas ter dados na visualização da GUI, mas também de escrever uma rotina que empurra os dados extraídos do Modelo para um stringa ser mostrado no depurador (e por extensão em um arquivo de texto sem formatação). Se eu puder pegar os dados do Modelo e traduzi-los livremente em texto sem alterar a Visualização ou o Modelo e apenas o Controlador, estou no caminho certo.

Dito isto, você precisará ter referências entre os diferentes componentes para que tudo funcione. O Controller precisa conhecer o View para enviar dados, o View precisa saber sobre o Controller para avisar quando uma alteração foi feita (como quando o Usuário clica em "Salvar" ou "Novo ..."). O Controller precisa saber sobre o Modelo para extrair os dados, mas eu argumentaria que o Modelo não deveria saber de mais nada.

Advertência: Eu venho de uma experiência totalmente Mac, Objective-C, Cocoa, que realmente leva você ao paradigma MVC, quer você queira ou não.

Philip Regan
fonte
Esse é definitivamente o meu objetivo. Meu principal problema é como configurar o modo de exibição - se deve ser uma classe com uma instância de cada caixa de diálogo e, em seguida, usar o View.Getters que chama Dialog.Getters ou se o Presenter deve chamar o Dialog.Getters diretamente ( isso parece muito intimamente ligado, por isso provavelmente não)?
trycatch
Eu acho que o apresentador / controlador deve ser totalmente responsável pelas exibições, então a última. Novamente, é provável que ocorra algum acoplamento, mas pelo menos se a direção da responsabilidade for clara, a manutenção deverá ser mais fácil a longo prazo.
Philip Regan
2
Certamente concordo que o P / C deve ser responsável pelo View, mas achei que parte do que deveria tornar o MVP poderoso era a capacidade de extrair toda a biblioteca da interface do usuário e conectar uma nova e com algumas massagens (dllimporting e outros enfeites) ser capaz de executar outro em seu lugar. Isso não seria mais difícil com o Controlador / Apresentador acessando os diálogos diretamente? Eu certamente não estou tentando argumentar, apenas entender melhor :)
trycatch
Eu acho que o poder real vem de duas direções: a primeira é que o View e o Model não têm nada a ver com a outra, e a segunda é que a maioria do trabalho de desenvolvimento, o mecanismo do aplicativo, é feita de maneira organizada. unidade, o controlador. Mas algum sangramento de responsabilidade está prestes a acontecer. Pelo menos a maioria das trocas de interfaces será feita no Controller e qualquer link a partir da View seria mínimo. Como já foi dito, é esperada e permitida alguma sangria de lógica. MVC não é uma bala mágica.
Philip Regan
O ponto importante para a dissociação é que o apresentador acessa SOMENTE a visualização por meio de interfaces bem definidas (independente da biblioteca da interface do usuário), e é assim que a biblioteca da interface do usuário pode ser substituída por outra (outra que implementará a mesma interface para o formulário / janela / diálogo / página / controlo / qualquer que seja)
Marcel Toth
2

Em geral, você deseja que seu modelo encapsule todas as interações com esse modelo. Por exemplo, suas ações de CRUD (Criar, Ler, Atualizar, Excluir) fazem parte do modelo. O mesmo vale para cálculos especiais. Há algumas boas razões para isso:

  • É mais fácil automatizar seus testes para este código
  • Mantém todas essas coisas importantes em um só lugar

No seu controlador (aplicativo MVC), tudo o que você está fazendo é coletar os modelos que você precisa usar na sua visualização e chamar as funções apropriadas no modelo. Quaisquer alterações no estado do modelo acontecem nesta camada.

Sua visualização simplesmente exibe os modelos que você preparou. Essencialmente, a visualização lê apenas o modelo e ajusta sua saída de acordo.

Mapeando o princípio geral para classes reais

Lembre-se de que suas caixas de diálogo são visualizações. Se você já possui uma classe de diálogo, não há motivo para criar outra classe "Visualizar". A camada Presenter vincula essencialmente o modelo aos controles na Visualização. A lógica de negócios e todos os dados importantes são armazenados no modelo.

Berin Loritsch
fonte