В шаблоне MVP должен ли View создавать экземпляр объекта Model на основе содержимого пользовательского интерфейса или просто передавать это содержимое в качестве параметров Presenter?

9

Я использую шаблон MVP в приложении для Android, которое я разрабатываю.

У меня есть в основном 4 элемента:

  1. AddUserView, где может быть добавлен новый пользователь:
  2. AddUserPresenter
  3. UserInfo (Pojo)
  4. UserInfoManager (логика работы и менеджер хранилища)

Мой вопрос:

Когда я нажимаю кнопку «Добавить» в AddUserView, он должен получить содержимое текстовых представлений, создать экземпляр нового UserInfo и передать его докладчику. Или AddUserView должен просто получить содержимое textViews и передать его в AddUserPresenter, который фактически создаст экземпляр UserInfo и передаст его UserInfoManager?

Rômulo.Edu
источник

Ответы:

8

Согласно описанию Мартина Фаулера о MVP ( http://martinfowler.com/eaaDev/uiArchs.html )

Фаулер говорит о части MVC «Вид»:

Первым элементом Potel является обработка представления как структуры виджетов, виджетов, которые соответствуют элементам управления модели Forms и Controls, и удаление любого разделения представления / контроллера. Представление MVP является структурой этих виджетов. Он не содержит поведения, которое описывает, как виджеты реагируют на взаимодействие с пользователем .

(Жирный упор мой)

Затем от ведущего:

Активная реакция на действия пользователя живет в отдельном объекте презентатора. Фундаментальные обработчики пользовательских жестов все еще существуют в виджетах, но эти обработчики просто передают управление докладчику .

Затем докладчик решает, как реагировать на событие. Потель обсуждает это взаимодействие в первую очередь с точки зрения действий над моделью, которые он выполняет с помощью системы команд и выборов. Здесь полезно отметить подход, заключающийся в упаковке всех правок модели в команду - это обеспечивает хорошую основу для обеспечения поведения отмены / повтора.

(Опять жирный акцент мой)

Таким образом, в соответствии с рекомендациями Фаулера, ваш Просмотр не должен нести ответственность за любое поведение в ответ на событие кнопки; который включает в себя создание экземпляра UserInfo. Ответственность за принятие решения о создании объекта принадлежит методу Presenter, которому перенаправляется событие пользовательского интерфейса.

Однако можно также утверждать, что обработчик события кнопки View также не должен отвечать за передачу содержимого вашего элемента textView, поскольку View должен просто пересылать событие кнопки в Presenter и ничего более.

В MVP для представления обычно реализован интерфейс, который может использовать докладчик для получения данных непосредственно из представления (при этом гарантируя, что докладчик по-прежнему не зависит от самого представления). Так как UserInfo является простым POJO, это может быть справедливо для вида , чтобы выставить геттер для UserInfo которой ведущий может забрать Фрон View через интерфейс.

// 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.
    }
}

Чем это отличается от передачи UserInfoнепосредственно в представление с помощью обработчика событий? Основное отличие состоит в том, что докладчик по-прежнему несет конечную ответственность за логику, которая вызывает создание UserInfoобъекта. то есть событие достигло докладчика до его создания UserInfo, что позволяет докладчику принять решение.

Представьте себе сценарий, в котором у вас была логика презентатора, в которой вы не хотели, UserInfoчтобы она создавалась на основе некоторого состояния в представлении. Например, если пользователь не установил флажок в представлении или вы проверили проверку по некоторому полю, которое было добавлено в UserInfo, но не удалось - ваш докладчик может содержать дополнительную проверку перед вызовом GetUserInfo- т.е.

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

    public void onSomethingClicked() {            

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

Эта логика остается внутри презентатора, и ее не нужно добавлять в представление. Если бы представление отвечало за вызов, GetUserInfo()то оно также отвечало бы за любую логику, окружающую его использование; это то, что пытается избежать шаблон MVP.

Таким образом, хотя метод, который создает, который UserInfoможет физически существовать в классе View, он никогда не вызывается из класса View, только из Presenter.

Конечно, если создание в UserInfoконечном итоге требует дополнительных проверок содержимого пользовательских виджетов ввода (например, преобразование строк, проверка и т. Д.), То было бы лучше выставить отдельные средства получения для этих вещей, чтобы проверка / преобразование строк могло занять место внутри докладчика - и тогда докладчик создает свой UserInfo.

В целом, вашей главной целью в отношении разделения между Presenter / View является обеспечение того, чтобы вам никогда не приходилось писать логику в представлении. Если вам когда-либо понадобится добавить ifоператор по какой-либо причине (даже если это ifоператор относительно состояния свойства виджета - проверка пустого текстового поля или логическое значение для флажка), то он принадлежит представителю.

Бен Коттрелл
источник
1
Отличный ответ @BenCottrell! Но у меня есть еще один :) Хорошая практика - называть методы презентатора так onSomethingClicked(), чтобы, когда пользователь нажимает «что-то», вызывается View presenter.onSomethingClicked()? Или мои методы презентатора должны быть названы как предполагаемые действия, в моем случае addUser()?
Rômulo.Edu
1
@regmoraes Хороший вопрос; и я думаю, что вы отметили небольшой запах в моем примере кода. PresenterЭто, конечно , отвечает за логику пользовательского интерфейса , а не логики домена, и с учетом конкретно к View, поэтому понятия , которые должны существовать такие понятия UI, поэтому метод назван onSomethingClicked()действительно является целесообразным. Оглядываясь назад, названия, которые я выбрал в моем примере выше, не совсем пахнут :-).
Бен Коттрелл
@BenCottrell Во-первых, большое спасибо за отличный ответ. Я понимаю, что это верно, если этот GetUserInfoметод в представлении, как вы упомянули (будет вызван докладчиком). Как насчет возможных ifусловий внутри GetUserInfoметода? Может быть, некоторые поля UserInfo будут установлены через реакцию пользователя? Сценарий: возможно, пользователь установит флажок, после чего некоторые новые компоненты (возможно, новый EditText) будут видны пользователю. Так что в этом случае GetUserInfoметод будет иметь условие if. В этом сценарии GetUserInfoдействует еще?
blackkara
1
@Blackkara Подумайте о том, чтобы рассматривать UserInfoкак модель представления (иначе «Модель представления») - в этом сценарии я бы добавил в него booleanсостояние флажка и состояние пустого / пустого значения Stringдля текстового поля UserInfo. Вы можете даже подумать о его переименовании, UserInfoViewModelесли это поможет понять, что POJO - это класс, единственная реальная цель которого - дать UserInfoPresenterинформацию о состоянии View.
Бен Коттрелл