Czy we wzorcu MVP widok powinien utworzyć instancję obiektu Model na podstawie zawartości interfejsu użytkownika, czy po prostu przekazać tę zawartość jako parametry do prezentera?

9

Używam wzorca MVP w aplikacji na Androida, którą opracowuję.

Mam w zasadzie 4 elementy:

  1. AddUserView, do którego można dodać nowego użytkownika:
  2. AddUserPresenter
  3. UserInfo (pojęcie)
  4. UserInfoManager (logika biznesu i menedżer pamięci)

Moje pytanie brzmi:

Kiedy nacisnę przycisk „Dodaj” w AddUserView, powinien on pobrać treść widoków tekstowych, utworzyć nową UserInfo i przekazać ją Prezenterowi. A może AddUserView powinien po prostu pobrać zawartość textViews i przekazać je do AddUserPresenter, który w rzeczywistości utworzy instancję UserInfo i przekaże ją do UserInfoManager?

Rômulo.Edu
źródło

Odpowiedzi:

8

Według opisu MVP Martina Fowlera ( http://martinfowler.com/eaaDev/uiArchs.html )

O części MVC „Widok” Fowler mówi:

Pierwszym elementem Potela jest traktowanie widoku jako struktury widżetów, widżetów odpowiadających kontrolkom modelu Forms and Controls i usunięcie wszelkich separacji widok / kontroler. Widok MVP jest strukturą tych widżetów. Nie zawiera żadnego zachowania opisującego reakcję widżetów na interakcję użytkownika .

(Moje pogrubione wyróżnienie)

Następnie prezenter:

Aktywna reakcja na działania użytkownika żyje w oddzielnym obiekcie prezentera. Podstawowe widżety dla gestów użytkownika nadal istnieją w widżetach, ale te uchwyty jedynie przekazują kontrolę prezenterowi .

Prezenter następnie decyduje, jak zareagować na zdarzenie. Potel omawia tę interakcję przede wszystkim w kontekście działań na modelu, które wykonuje za pomocą systemu poleceń i selekcji. Przydatną rzeczą do podkreślenia tutaj jest podejście polegające na pakowaniu wszystkich zmian w modelu za pomocą polecenia - zapewnia to dobrą podstawę do zachowania cofania / ponawiania.

(Ponownie, moje pogrubione wyróżnienie)

Tak więc, zgodnie z wytycznymi Fowlera, Twój Widok nie powinien być odpowiedzialny za jakiekolwiek zachowanie w odpowiedzi na zdarzenie przycisku; który obejmuje utworzenie wystąpienia UserInfo. Odpowiedzialność za podjęcie decyzji o utworzeniu obiektu należy do metody Presenter, do której przekazywane jest zdarzenie interfejsu użytkownika.

Jednak można również argumentować, że moduł obsługi zdarzenia przycisku Widoku nie powinien być również odpowiedzialny za przekazywanie zawartości textViewktóregoś z nich, ponieważ Widok powinien po prostu przekazywać zdarzenie przycisku do Prezentera i nic więcej.

W przypadku MVP widok często implementuje interfejs, za pomocą którego prezenter może pobierać dane bezpośrednio z widoku (jednocześnie zapewniając, że prezenter nadal jest niezależny od samego widoku). Ponieważ UserInfo jest prostym POJO, może być ważne, aby widok ujawnił getter dla UserInfo, który Prezenter może pobrać z Widoku poprzez interfejs.

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

Czym to się różni od przekazania UserInfobezpośrednio do widoku za pomocą procedury obsługi zdarzeń? Główną różnicą jest to, że prezenter nadal jest ostatecznie odpowiedzialny za logikę, która powoduje utworzenie UserInfoobiektu. tzn. wydarzenie dotarło do Prezentera przed jego utworzeniem UserInfo, umożliwiając Prezenterowi podjęcie decyzji.

Wyobraź sobie scenariusz, w którym masz logikę prezentera, w której nie chcesz, UserInfoaby była tworzona na podstawie pewnego stanu w widoku. Na przykład, jeśli użytkownik nie zaznaczył pola wyboru w widoku lub miałeś sprawdzenie poprawności względem pola, które ma zostać dodane do UserInfo, które nie powiodło się - Twój prezenter może zawierać dodatkowe sprawdzenie przed wywołaniem GetUserInfo- tj.

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

    public void onSomethingClicked() {            

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

Ta logika pozostaje w prezenterie i nie musi być dodawana do widoku. Gdyby widok był odpowiedzialny za wywołanie GetUserInfo(), byłby również odpowiedzialny za logikę związaną z jego użyciem; tego właśnie próbuje uniknąć wzór MVP.

Tak więc, chociaż metoda, która tworzy, która UserInfofizycznie może istnieć w klasie View, nigdy nie jest wywoływana z klasy View, tylko z Presenter.

Oczywiście, jeśli tworzenie UserInfokońcowych elementów wymaga dodatkowej kontroli zawartości widżetów wprowadzanych przez użytkownika (np. Konwersja ciągów, sprawdzanie poprawności itp.), Lepiej byłoby udostępnić poszczególne moduły pobierające dla tych rzeczy, aby można było sprawdzić poprawność / konwersję ciągów miejsce w Prezenterie - a następnie prezenter tworzy Twój UserInfo.

Ogólnie rzecz biorąc, twoim głównym celem w odniesieniu do oddzielenia Prezentera / Widoku jest zapewnienie, że nigdy nie będziesz musiał pisać logiki w widoku. Jeśli kiedykolwiek będziesz musiał dodać ifinstrukcję z dowolnego powodu (nawet jeśli jest to ifinstrukcja dotycząca stanu właściwości widgetu - zaznaczenie pustego pola tekstowego lub wartość logiczna dla pola wyboru), to należy do prezentera.

Ben Cottrell
źródło
1
Świetna odpowiedź @ BenCottrell! Ale mam inny :) Czy dobrą praktyką jest nazywanie metod prezenterów jako onSomethingClicked(), więc kiedy użytkownik kliknie „coś”, wywołuje widok presenter.onSomethingClicked()? Czy w moim przypadku moje metody prezentera powinny być nazwane zamierzonymi działaniami addUser()?
Rômulo.Edu
1
@regmoraes Dobre pytanie; i myślę, że podkreśliłeś delikatny zapach w moim przykładowym kodzie. PresenterJest oczywiście odpowiedzialny za UI logiki zamiast logiki domeny, a specjalnie dostosowane do View, więc pojęcia, które powinny istnieć to pojęcia UI, więc metoda nazwana onSomethingClicked()jest rzeczywiście właściwe. Z perspektywy czasu nazwa, którą wybrałem w powyższym przykładzie, nie pachnie całkiem dobrze :-).
Ben Cottrell,
@BenCottrell Po pierwsze wielkie dzięki za świetną odpowiedź. Rozumiem, że poprawne jest posiadanie tej GetUserInfometody w widoku, o którym wspomniałeś (zostanie uruchomiony z prezentera) Co z możliwymi ifwarunkami w GetUserInfometodzie? Może niektóre pola UserInfo zostaną ustawione na podstawie reakcji użytkownika? Scenariusz: być może użytkownik zaznaczy pole wyboru, a następnie niektóre nowe komponenty (być może nowy EditText) będą widoczne dla użytkownika. W takim przypadku GetUserInfometoda będzie miała warunek if. Czy w tym scenariuszu GetUserInfonadal obowiązuje?
blackkara,
1
@Blackkara Zastanów się nad traktowaniem UserInfojak model widoku (inaczej „View Model”) - W tym scenariuszu dodałbym do tego booleanstan pola wyboru oraz pusty / zerowalny Stringstan pola tekstowego UserInfo. Możesz nawet rozważyć zmianę jego nazwy, UserInfoViewModeljeśli to pomaga myśleć w kategoriach POJO, która jest prawdziwą klasą, której jedynym prawdziwym celem jest umożliwienie UserInfoPresenterznalezienia informacji o stanie Widoku.
Ben Cottrell,