MVP - prośba o sprawdzenie implementacji i wyjaśnienie kilku wątpliwości

MVP - prośba o sprawdzenie implementacji i wyjaśnienie kilku wątpliwości
Degusto
  • Rejestracja:ponad 11 lat
  • Ostatnio:około rok
  • Lokalizacja:Piła
  • Postów:70
0

Witam, ostatnio zacząłem uczyć się wzorca MVP, ale nie jestem do końca pewny czy dobrze go rozumiem.
Tak przedstawia się kod, który napisałem.

Interfejsy:

Kopiuj
interface ILoginPresenter
{
    bool CanLogIn();
}

interface ILoginView
{
    LoginData GetLoginData();
}

Modele:

Kopiuj
class Logger
    {
        public LoginData LoginData { get; set; }

        public Logger(LoginData loginData)
        {
            LoginData = loginData;
        }

        public bool CanLogIn()
        {
            var users = new UsersDataContext();
            var login = LoginData.Login;
            var password = LoginData.Password;

            return users.Users.
                Where(user => user.Login == login && user.Password == password).
                Count() == 1;
        }
    }

public class LoginData
    {
        public string Login { get; set; }
        public string Password { get; set; }
    }

Prezenter:

Kopiuj
class LoginPresenter : ILoginPresenter
    {
        private readonly ILoginView view;
        
        public LoginPresenter(ILoginView view)
        {
            this.view = view;
        }

        public bool CanLogIn()
        {
            LoginData loginData = view.GetLoginData();
            var logger = new Logger(loginData);

            return logger.CanLogIn();
        }
    }

Widok:

Kopiuj
public partial class LoginView : Form, ILoginView
    {
        private readonly ILoginPresenter presenter;

        public LoginView()
        {
            InitializeComponent();

            presenter = new LoginPresenter(this);
        }

        public LoginData GetLoginData()
        {
            string login = LoginTextBox.Text;
            string password = PasswordTextBox.Text;

            return new LoginData { Login = login, Password = password };
        }

        private void LogInButton_Click(object sender, EventArgs e)
        {
            bool canLogIn = presenter.CanLogIn();

            MessageBox.Show(canLogIn ? "Można." : "Nie można.");
        }
    }

No i kilku rzeczy nie jestem pewny lub nie rozumiem, a mianiowicie:

  1. Między widokiem a prezenterem istnieje relacja jeden do jednego?
  2. Mogę w widoku wykorzystywać klasy z modeli - tak jak tutaj np. LoginData?
  3. W prezenterze powinienem używać już gotowych klas(modeli?), które mają w sobie jakąś logikę?
  4. Prezenter powinien działać na zasadzie prześlij z widoku do modelu i na odwrót, np. update do bazy albo pobieranie danych z bazy?
  5. Logika w prezenterze powinna dotyczyć tylko pośrednictwa między widokiem, a modelem?
  6. Pola view i presenter powinny być readonly?
  7. Czy moja implementacja jest poprawna? Wiem, że przykład banalny, ale chciałem się skupić na prawidłowym zrozumieniu MVP.
  8. W klasie LoginPresenter w funkcji CanLogIn powinienem użyć ILoginData i ILogger czy LoginData i Logger?
mr_jaro
  • Rejestracja:ponad 13 lat
  • Ostatnio:około 3 lata
  • Lokalizacja:Grudziądz/Bydgoszcz
  • Postów:5300
1

Co prawda inny język, ale myślę, że ten link rozjaśni różnice między mvc a mvp i ich zasadę działania. http://blog.karolak.it/2010/11/17/wzorzec-projektowy-zastosowany-w-cakephp-mvc-czy-mvp/


It's All About the Game.
Degusto
  • Rejestracja:ponad 11 lat
  • Ostatnio:około rok
  • Lokalizacja:Piła
  • Postów:70
0

Czyli weźmy sobie przykład z logowania. Widok wywołuje metodę w prezenterze. Prezenter pobiera dane z widoku, a następnie pobiera z modelu dane o użytkowniku przez login. Teraz prezenter sprawdza czy istnieje taki użytkownik(jest różny od null), czy hasła pasują i wysyła do widoku dane o użytkowniku lub null jeśli nie ma takiego?

A jeśli chciałbym dodać walidatory(jakieś tam klasy związane z walidacją) to gdzie powinny się ona znajdować? W folderze z prezenterami? Czy może lepiej byłoby np. stworzyć statyczną klasę z metodami do walidacji albo metody zamieścić już bezpośrednio w prezenterze?

edytowany 1x, ostatnio: Degusto
somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:dzień
  • Lokalizacja:Wrocław
1
Degusto napisał(a):
  1. Między widokiem a prezenterem istnieje relacja jeden do jednego?
  2. Mogę w widoku wykorzystywać klasy z modeli - tak jak tutaj np. LoginData?
  3. W prezenterze powinienem używać już gotowych klas(modeli?), które mają w sobie jakąś logikę?
  4. Prezenter powinien działać na zasadzie prześlij z widoku do modelu i na odwrót, np. update do bazy albo pobieranie danych z bazy?
  5. Logika w prezenterze powinna dotyczyć tylko pośrednictwa między widokiem, a modelem?
  6. Pola view i presenter powinny być readonly?
  7. Czy moja implementacja jest poprawna? Wiem, że przykład banalny, ale chciałem się skupić na prawidłowym zrozumieniu MVP.
  8. W klasie LoginPresenter w funkcji CanLogIn powinienem użyć ILoginData i ILogger czy LoginData i Logger?
  1. Generalnie jeden widok wymaga jednego prezentera, więc owszem, jest to powiązanie 1:1. (Lepiej unikać słowa "relacja".)
  2. Niby możesz, chociaż przy prawdziwej aplikacji te warstwy mogą być od siebie fizycznie odseparowane, więc w Widoku możesz nie mieć dostępu do Modelu. Dlatego dobrą praktyką jest tworzenie viewmodeli - małych lekkich klas zawierających dane potrzebne do wyświetlenia/pobrania z Widoku. Twój LoginData to właśnie taki viewmodel, a prawdziwa klasa 'User' zmapowana z bazą danych może mieć dużo więcej właściwości.
  3. Tak.

4 i 5) Prezenter zajmuje się logiką prezentacji, czyli:
a) pobiera dane z Modelu i przekazuje je odpowiedniemu Widokowi do wyświetlenia;
b) odbiera dane z Widoku i przekazuje je do przetwarzania odpowiedniemu Modelowi;
c) po wykonaniu jakiejś akcji przez Widok/Model, decyduje co zrobić, czy np. wyświetlić odświeżony Widok, czy też przekierować do innego Widoku;
d) w przypadku nastąpienia jakiegoś błędu, czy to po stronie Widoku czy Modelu, wyświetla Widok z komunikatem o błędu;
e) decyduje, które sekcje Widoku powinny zostać wyświetlone użytkownikowi, które są możliwe do edycji, a które dostępne tylko w trybie do odczytu.

  1. Tak, właściwie jak wszystkie pola.
  2. Implementacja MVP właściwie tak, sam kod niekoniecznie:

a) Klasa Logger:

  1. Jej nazwa - "Logger" to zazwyczaj coś, co służy do logowania (w sensie audytu) tego, co robi program np. do pliku tekstowego.
  2. Czemu służy publiczna właściwość LoginData?
  3. Czemu jest inicjalizowana w konstruktorze?
  4. Dlaczego po prostu metoda CanLogIn nie przyjmie danych w argumencie i nie zwróci wyniku? Tak byłoby dużo prościej, krócej i uniknąłbyś stanu w tej klasie. Obecnie można zmienić wartość LoginData, przed wywołaniem CanLogIn, psując w ten sposób działanie aplikacji.
  5. Po co Where, skoro do Count też możesz podać warunek?

b) Klasa LoginData:

Tu też masz stan, a przecież można zrobić prywatne settery i konstruktor ustawiający te wartości. Dzięki czemu obiekt staje się niezmienny, więc nie da się go zepsuć po utworzeniu. A do tego nie trzeba pisać nazw właściwości przy tworzeniu obiektu.

  1. Nie ma sensu robienie interfejsów tylko po to, żeby były. Jeśli nie ma takiej potrzeby, to nie ma po co tracić na to czasu. Moim zdaniem nie ma nawet sensu istnienie IPresentera. Interfejsy mają sens, jeśli poszczególne klasy istnieją w oddzielnych warstwach/modułach aplikacji, których nie chcesz wiązać ze sobą na sztywno. W MVP jedynie Widoki muszą być interfejsami, pozostałe elementy według potrzeb i uznania. A już w ogóle nie ma sensu robienie interfejsu dla DTO takiego jak LoginData.
Degusto napisał(a):

Czyli weźmy sobie przykład z logowania. Widok wywołuje metodę w prezenterze. Prezenter pobiera dane z widoku, a następnie pobiera z modelu dane o użytkowniku przez login. Teraz prezenter sprawdza czy istnieje taki użytkownik(jest różny od null), czy hasła pasują i wysyła do widoku dane o użytkowniku lub null jeśli nie ma takiego?

Absolutnie nie, Prezenter niczego nie sprawdza, nie wykonuje logiki biznesowej, nie operuje na bazie, nie robi takich rzeczy. On co najwyżej decyduje o tym, czy wyświetla Widok dostępny dla zalogowanego użytkownika, czy taki z komunikatem o braku dostępu.

A jeśli chciałbym dodać walidatory(jakieś tam klasy związane z walidacją) to gdzie powinny się ona znajdować? W folderze z prezenterami? Czy może lepiej byłoby np. stworzyć statyczną klasę z metodami do walidacji albo metody zamieścić już bezpośrednio w prezenterze?

Walidacja logiki biznesowej to część Modelu.
Walidacja danych wprowadzonych przez użytkownika, to część Widoku - czyli to on powinien zadbać, czy dane przesłane do Prezentera mają odpowiedni typ i format. Prezenter to tylko pośrednik, sam niczego nie powinien walidować.
Statyczna klasa to generalnie zły pomysł, ale to zależy co i kiedy chcesz walidować.

Degusto
  • Rejestracja:ponad 11 lat
  • Ostatnio:około rok
  • Lokalizacja:Piła
  • Postów:70
0

Dzięki, wiele rzeczy mi rozjaśniłeś :)

Poprawiłem trochę kod.

Interfejs:

Kopiuj
interface ILoginView
    {
        LoginData GetLoginData();
    }

Widok:

Kopiuj
 public partial class LoginView : Form, ILoginView
    {
        private readonly LoginPresenter presenter;

        public LoginView()
        {
            InitializeComponent();

            presenter = new LoginPresenter(this);
        }

        public LoginData GetLoginData()
        {
            string login = LoginTextBox.Text;
            string password = PasswordTextBox.Text;

            return new LoginData(login, password);
        }

        private void LogInButton_Click(object sender, EventArgs e)
        {
            bool canLogIn = presenter.CanLogIn();

            MessageBox.Show(canLogIn ? "Można." : "Nie można.");
        }
    }

Prezenter:

Kopiuj
class LoginPresenter
    {
        private readonly ILoginView view;
        
        public LoginPresenter(ILoginView view)
        {
            this.view = view;
        }

        public bool CanLogIn()
        {
            LoginData loginData = view.GetLoginData();
            var logger = new Logger();

            return logger.CanLogIn(loginData.Login, loginData.Password);
        }
    }

Model:

Kopiuj
class Logger
    {
        public bool CanLogIn(string login, string password)
        {
            var users = new UsersDataContext();

            return users.Users.
                Count(user => user.Login == login && user.Password == password) == 1;
        }
    }

ViewModel:

Kopiuj
public class LoginData
    {
        public string Login { get; private set; }
        public string Password { get; private set; }

        public LoginData(string login, string password)
        {
            this.Login = login;
            this.Password = password;
        }
    }

Mam jeszcze kilka pytań:

  1. Gdzie powinienem przechowywać ViewModele, w prezenterze czy widoku?
  2. Powinienem w prezenterze przetworzyć wyniki z widoku do modelu tak jak zrobiłem to w prezenterze w metodzie CanLogIn?
  3. Jakbyś nazwał lepiej nazwał klasę Logger?
  4. Kod wygląda teraz lepiej?
  5. Mógłbym mieć w prezenterze zdarzenia np. LoginFailed i LoginSuccess, a widok podpinał by pod nie swoje metody?
somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:dzień
  • Lokalizacja:Wrocław
1
  1. Pytasz o obiekty ViewModeli (tych się w ogóle nie przechowuje tylko tworzy na czas użycia) czy definicje klas? Jeśli to drugie, to musi to być miejsce dostępne zarówno dla Prezenterów jak i konkretnych Widoków, w praktyce np. gdzieś obok definicji interfejsów Widoków.
  2. Tak.
  3. Np. AuthenticationService.
  4. Znacznie. Tylko korzystanie z UserDataContext powinno być w zawarte w using i zamiast .Count(warunek) == 1 można zrobić Any(warunek).
  5. No niby mógłbyś... Ale jaki to miałoby cel? Bo zdarzenia w ogólności mają cel taki, że wiele obiektów może zostać poinformowanych o ich wystąpieniu, natomiast w przypadku powiązania 1 Prezenter : 1 Widok, takie coś z definicji nie nastąpi.
    Ja generalnie jestem przeciwnikiem opierania przebiegu programu o zdarzenia. Kod zdarzeniowy jest nieczytelny i trudniejszy w debugowaniu.

Spróbuj pisać aplikację, która coś odczytuje i zapisuje do jakiejś bazy/pliku, bo ten przykład jest zbyt prosty, żeby pokazać o co chodzi w MVP.

Degusto
  • Rejestracja:ponad 11 lat
  • Ostatnio:około rok
  • Lokalizacja:Piła
  • Postów:70
0

Dzięki wielkie, wiele mi wyjaśniłeś. Jutro spróbuję napisać jakąś prostą aplikację i wrzucę kod.

EDIT:
Napisałem coś takiego, mam nadzieję, że nie jest zbyt prosty:
https://github.com/Degusto/MVP-Sample

edytowany 1x, ostatnio: Degusto
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)