No padrão MVP, a View deve instanciar um objeto Model com base no conteúdo da interface do usuário ou apenas passar esses conteúdos como parâmetros para o Presenter?

9

Estou usando o padrão MVP em um aplicativo Android que estou desenvolvendo.

Eu tenho basicamente 4 elementos:

  1. O AddUserView, onde um novo usuário pode ser adicionado:
  2. O AddUserPresenter
  3. O UserInfo (o pojo)
  4. O UserInfoManager (lógica de negócios e gerenciador de armazenamento)

Minha pergunta é:

Quando pressiono o botão "Adicionar" no AddUserView, ele deve obter o conteúdo das visualizações de texto, instanciar uma nova UserInfo e passá-la ao Presenter. Ou o AddUserView deve apenas pegar o conteúdo do textViews e passá-lo para o AddUserPresenter, que de fato instanciará o UserInfo e passará para o UserInfoManager?

Rômulo.Edu
fonte

Respostas:

8

De acordo com a descrição do MVP de Martin Fowler ( http://martinfowler.com/eaaDev/uiArchs.html )

Da parte View do MVC, Fowler diz:

O primeiro elemento do Potel é tratar a visualização como uma estrutura de widgets, widgets que correspondem aos controles do modelo Forms and Controls e remover qualquer separação de visualização / controlador. A visão do MVP é uma estrutura desses widgets. Ele não contém nenhum comportamento que descreve como os widgets reagem à interação do usuário .

(Negrito ênfase minha)

Então do apresentador:

A reação ativa ao usuário age em um objeto apresentador separado. Os manipuladores fundamentais para gestos do usuário ainda existem nos widgets, mas esses manipuladores apenas passam o controle para o apresentador .

O apresentador decide como reagir ao evento. Potel discute essa interação principalmente em termos de ações no modelo, o que é feito por um sistema de comandos e seleções. Uma coisa útil a destacar aqui é a abordagem de empacotar todas as edições do modelo em um comando - isso fornece uma boa base para fornecer o comportamento de desfazer / refazer.

(Novamente, ênfase em negrito)

Portanto, de acordo com as diretrizes da Fowler, sua exibição não deve ser responsável por nenhum comportamento em resposta ao evento do botão; que inclui a criação de uma instância de UserInfo. A responsabilidade de decidir criar um objeto pertence ao método Presenter para o qual o evento da interface do usuário é encaminhado.

No entanto, também se pode argumentar que o manipulador de eventos de botão do View também não deve ser responsável por transmitir o conteúdo do textViewmesmo, pois o View deve apenas encaminhar o evento button para o Presenter e nada mais.

Com o MVP, é comum a exibição implementar uma interface que o apresentador pode usar para coletar dados diretamente da exibição (enquanto garante que o apresentador ainda seja independente da própria exibição). Como o UserInfo é um POJO simples, pode ser válido que a exibição exponha um getter para a UserInfo que o Presenter possa selecionar da View por meio de uma interface.

// The view would implement IView
public interface IView {

    public UserInfo GetUserInfo();
}

// Presenter
public class AddUserPresenter {

    private IView addUserView;

    public void SetView(IView view) {
        addUserView = view
    }

    public void onSomethingClicked() {

        UserInfo userInfo = addUserView.GetUserInfo();
        // etc.
    }
}

Como isso é diferente de passar o UserInfodiretamente para a exibição usando o manipulador de eventos? A principal diferença é que o apresentador ainda é o responsável final pela lógica que causa UserInfoa criação de um objeto. isto é, o evento chegou ao apresentador antes da criação do UserInfo, permitindo ao apresentador tomar a decisão.

Imagine um cenário em que você tivesse lógica de apresentador em que não desejasse que isso UserInfofosse criado com base em algum estado da exibição. Por exemplo, se o usuário não marcou uma caixa de seleção na exibição, ou você teve uma verificação de validação em algum campo a ser adicionado à UserInfo que falhou - seu apresentador pode conter uma verificação adicional antes de ligar GetUserInfo- ou seja

    private boolean IsUsernameValid() {
        String username = addUserView.GetUsername();
        return (username != null && !username.isEmpty());
    }

    public void onSomethingClicked() {            

        if (IsUsernameValid()) {
            UserInfo userInfo = addUserView.GetUserInfo();
            // etc.
        }
    }

Essa lógica permanece dentro do apresentador e não precisa ser adicionada à exibição. Se a visão fosse responsável pela chamada GetUserInfo(), ela também seria responsável por qualquer lógica que envolvesse seu uso; que é o que o padrão MVP está tentando evitar.

Portanto, embora o método que cria que UserInfopossa existir fisicamente na classe View, nunca seja chamado a partir da classe View, apenas a partir do Presenter.

Obviamente, se a criação das UserInfofinalizações exigir verificações adicionais em relação ao conteúdo dos widgets de entrada do usuário (por exemplo, conversão de string, validação etc.), seria melhor expor getters individuais para essas coisas, para que a validação / conversão de string possa demorar coloque dentro do Presenter - e, em seguida, o apresentador cria o seu UserInfo.

No geral, seu principal objetivo em relação à separação entre o Presenter / View é garantir que você nunca precise escrever lógica na exibição. Se você precisar adicionar uma ifdeclaração por qualquer motivo (mesmo que seja uma ifdeclaração sobre o estado de uma propriedade do widget - marcando uma caixa de texto vazia ou um booleano para uma caixa de seleção), ela pertence ao apresentador.

Ben Cottrell
fonte
11
Ótima resposta @BenCottrell! Mas eu tenho outro :) É uma boa prática nomear os métodos do apresentador como onSomethingClicked(), portanto, quando o usuário clica em "alguma coisa", o View chama presenter.onSomethingClicked()? Ou meus métodos de apresentador devem ser nomeados como as ações pretendidas, no meu caso addUser()?
Romulo.Edu 12/03
11
@regmoraes Boa pergunta; e acho que você destacou um leve odor no meu código de exemplo. A Presenteré naturalmente responsável pela lógica de interface do usuário, em vez de lógica de domínio, e adaptado especificamente para o View, portanto, conceitos que devem existir são conceitos de interface do usuário, portanto, um método chamado onSomethingClicked()é realmente apropriado. Em retrospectiva, o nome que escolhi no meu exemplo acima não parece muito certo :-).
Ben Cottrell
@ BenCottrell Em primeiro lugar, muito obrigado pela ótima resposta. Entendo que é válido ter esse GetUserInfométodo na visualização, como você mencionou (será acionado pelo apresentador) E as possíveis ifcondições dentro do GetUserInfométodo? Talvez alguns campos do UserInfo sejam definidos via reação do usuário? Um cenário: talvez o usuário marque uma caixa de seleção, então alguns novos componentes (talvez um novo EditText) estejam visíveis para o usuário. Portanto, nesse caso, o GetUserInfométodo terá se condição. Nesse cenário GetUserInfoainda é válido?
blackkara
11
@Blackkara Considere tratar UserInfocomo um modelo de exibição (também conhecido como "Exibir modelo") - Nesse cenário, adicionaria o booleanestado da caixa de seleção e o Stringestado vazio / anulável da caixa de texto UserInfo. Você pode até renomeá-lo, UserInfoViewModelse isso ajudar a pensar em termos de o POJO ser uma classe cujo único objetivo real é permitir que você UserInfoPresenterdescubra informações sobre o estado View.
precisa