Czysta architektura: co to jest model widoku?

13

W swojej książce „Czysta architektura” wujek Bob mówi, że prezenter powinien umieścić otrzymane dane w czymś, co nazywa „modelem widoku”.

wprowadź opis zdjęcia tutaj

Czy to to samo, co „ViewModel” z wzorca projektowego Model-View-ViewModel (MVVM), czy jest to prosty obiekt do przesyłania danych (DTO)?

Jeśli to nie prosta DTO, jak to się odnosi do widoku? Czy widok pobiera z niego aktualizacje za pośrednictwem relacji Obserwator?

Domyślam się, że bardziej przypomina ViewModel z MVVM, ponieważ w rozdziale 23 swojej książki Robert Martin mówi:

Zadaniem [Prezentera] jest przyjęcie danych z aplikacji i sformatowanie ich do prezentacji, aby widok mógł po prostu przenieść je na ekran. Na przykład, jeśli aplikacja chce, aby data była wyświetlana w polu, przekaże Prezenterowi obiekt Data. Następnie Prezenter sformatuje te dane w odpowiedni ciąg i umieści je w prostej strukturze danych o nazwie Model widoku, w której widok może je znaleźć.

Oznacza to, że widok jest w jakiś sposób połączony z ViewModel, w przeciwieństwie do zwykłego odbierania go jako argumentu funkcji (na przykład w przypadku DTO).

Innym powodem, dla którego tak myślę, jest to, że jeśli spojrzysz na obraz, Prezenter używa Modelu widoku, ale nie widoku. Podczas gdy Prezenter używa zarówno granicy wyjściowej, jak i danych wyjściowych DTO.

Jeśli nie jest to ani DTO, ani ViewModel firmy MVVM, wyjaśnij, co to jest.

Fearnbuster
źródło
Myślę, że odpowiedź brzmi „to zależy”. Jeśli jest to aplikacja internetowa, model widoku jest w zasadzie DTO, ponieważ ostatecznie jest serializowany jako ciąg HTML. W przeciwnym razie model widoku jest po prostu wyspecjalizowanym obiektem do wyświetlania danych w widoku.
Greg Burghardt
W MVVM (WPF, aplikacji WinForms) ViewModeljest nakładką na Controller, Presenteri ViewModelw czystej architektury wuja Boba.
Fabio
@Greg Burghardt - Kiedy ViewModel jest specjalistyczną strukturą danych, w jaki sposób View jest powiadamiany o zmianach?
Fearnbuster,
@Fabio - Jeśli prawidłowo zrozumiem twoje powiedzenie, że we wzorze MVVM ViewModel jest równoważny wszystkim komponentom, które znajdują się w najbardziej lewej grupie diagramu? Jeśli dotyczy to architektury wuja Boba, to dlaczego umieszcza osobno kontrolera i prezentera?
Fearnbuster
Myślę, że rozdzielił moduły obsługi danych wejściowych i wyjściowych na różne obiekty / klasy. W MVVM może to być Controller-> ICommandi Presenter-> data-binding mechanism.
Fabio

Odpowiedzi:

17

Czy to to samo, co „ViewModel” z wzorca projektowego Model-View-ViewModel (MVVM)

Nie.

To byłoby tak :

wprowadź opis zdjęcia tutaj

To ma cykle. Wujek Bob starannie unikał cykli .

Zamiast tego masz to:

wprowadź opis zdjęcia tutaj

Który z pewnością nie ma cykli. Ale zastanawiasz się, skąd widok wie o aktualizacji. Za chwilę do tego dojdziemy.

czy jest to prosty obiekt do przesyłania danych (DTO)?

Aby zacytować Boba z poprzedniej strony:

Jeśli chcesz, możesz użyć podstawowych struktur lub prostych obiektów do przesyłania danych. Możesz też spakować go do haszapy lub skonstruować w obiekt.

Czysta architektura p207

Tak więc, jeśli chcesz.

Ale mocno podejrzewam, że to, co cię naprawdę denerwuje, to :

wprowadź opis zdjęcia tutaj

To urocze małe nadużycie UML kontrastuje kierunek zależności kodu źródłowego z kierunkiem przepływu kontroli. Tutaj można znaleźć odpowiedź na twoje pytanie.

W relacji za pomocą:

wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj

przepływ kontroli idzie w tym samym kierunku co zależność kodu źródłowego.

W relacji wykonawczej:

wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj

przepływ kontroli zwykle idzie w przeciwnym kierunku niż w zależności od kodu źródłowego.

Co oznacza, że ​​naprawdę na to patrzysz:

wprowadź opis zdjęcia tutaj

Powinieneś być w stanie zobaczyć, że przepływ kontroli nigdy nie dostanie się od Prezentera do Widoku.

Jak to możliwe? Co to znaczy?

Oznacza to, że widok ma własny wątek (co nie jest takie niezwykłe) lub (jak wskazuje @Euphoric) przepływ kontroli pojawia się w widoku z czegoś, co nie zostało tutaj przedstawione.

Jeśli jest to ten sam wątek, widok będzie wiedział, kiedy model widoku jest gotowy do odczytu. Ale jeśli tak jest, a widok jest graficznym interfejsem użytkownika, trudno będzie mu odmalować ekran, gdy użytkownik przesunie go, czekając na DB.

Jeśli widok ma własny wątek, wówczas ma własny przepływ kontroli. Oznacza to, że aby to zaimplementować, widok będzie musiał sondować model widoku, aby zauważyć zmiany.

Ponieważ Prezenter nie wie, że Widok istnieje, a Widok nie wie, że Prezenter istnieje, nie mogą do siebie dzwonić. Nie mogą rzucać na siebie wydarzeniami. Wszystko, co może się zdarzyć, to Prezenter zapisze w View-Model, a View przeczyta View-Model. Kiedy tylko masz na to ochotę.

Zgodnie z tym diagramem jedyną rzeczą, którą widok i prezenter udostępniają, jest znajomość modelu widoku. I to tylko struktura danych. Więc nie oczekuj, że będzie się zachowywał.

Może się to wydawać niemożliwe, ale można sprawić, że zadziała, nawet jeśli model widoku jest złożony. Jedno małe zaktualizowane pole to wszystko, co widok musiałby sondować, aby wykryć zmianę.

Teraz możesz oczywiście nalegać na użycie wzorca obserwatora lub sprawić, by jakieś ramowe rzeczy ukryły przed tobą ten problem, ale proszę zrozum, że nie musisz.

Oto trochę zabawy, którą miałem ilustrując przepływ kontroli:

wprowadź opis zdjęcia tutaj

Zauważ, że ilekroć widzisz przepływ idący wbrew wskazanym wcześniej kierunkom, to, co widzisz, powraca. Ta sztuczka nie pomoże nam dostać się do Widoku. No chyba, że ​​najpierw wrócimy do czegoś, co nazywamy Kontrolerem. Lub możesz po prostu zmienić projekt , aby uzyskać dostęp do widoku. To także naprawia coś, co wygląda jak początek problemu jo-jo z Data Access i jego interfejsem.

Jedyną inną rzeczą do nauczenia się tutaj jest to, że Interoperator Przypadków Użytkowych może właściwie wywoływać rzeczy w dowolnej kolejności, o ile chce wywołać prezentera jako ostatni.

candied_orange
źródło
Wielkie dzięki za odpowiedź, widziałem twoje odpowiedzi na różne inne pytania dotyczące Czystej Architektury. Czy sugerujesz, aby widok stale sprawdzał flagę, na przykład w modelu widoku, aby sprawdzić, czy nastąpiły jakieś zmiany? Czy wówczas widok musiałby ponownie wyświetlać cały model widoku, czy powinienem użyć zestawu zagnieżdżonych flag, aby wskazać, które dane zostały zmienione?
Fearnbuster,
Widok nie musi stale odpytywać. Na przykład przeglądarka Web 1.0 odpytuje tylko wtedy, gdy użytkownik kliknie przycisk Przeładuj. Ciągłe pobieranie jest decyzją projektową, która powinna uwzględniać rzeczywiste potrzeby użytkowników. Mówię tylko, że to możliwe. Celem pola aktualizacji jest szybkie wykrywanie aktualizacji. Potrzebne tylko, jeśli widok modelu jest złożony. Zastanów się również, co się stanie, jeśli widok będzie czytał, gdy prezenter jest w trakcie aktualizacji.
candied_orange
Ok, wielkie dzięki za pomoc. Jeśli / kiedy podążasz za tą architekturą, czy jest to technika, której zwykle używasz?
Fearnbuster,
1
Myślę, że istnieje duży błąd, że ta odpowiedź grupuje zależność projektową i zależność od czasu wykonywania. Oba mogą być różne.
Euforyczny
1
@Euforia Dlaczego dziękuję. Wiążę je razem, ponieważ jeśli nie masz zależności kodu źródłowego od czegoś, nie możesz do tego użyć odwołania do środowiska wykonawczego, ponieważ nie rozumiesz, co to jest. Wszystko, co możesz zrobić, to przechowywać odnośnik tak jak kolekcja. Jeśli to pomyłka, chciałbym to zrozumieć.
candied_orange
2

Uważam ten problem za zbyt mylący i zajęłoby dużo tekstu i czasu, aby poprawnie wyjaśnić problem, ponieważ uważam, że źle rozumiesz zarówno czystą architekturę Martina, jak i MVVM.

Pierwszą rzeczą, na którą należy zwrócić uwagę, jest to, że zamieszczony schemat jest niekompletny. Pokazuje tylko „logikę biznesową”, ale brakuje jakiegoś „orkiestratora”, który faktycznie sprawia, że ​​części poruszają się we właściwej kolejności. wprowadź opis zdjęcia tutaj

Kod orkiestratora byłby tak prosty jak

string Request(string request) // returns response
{
    Controller.Run(data);
    Presenter.Run();
    return View.Run();
}

Wydaje mi się, że słyszałem, jak Martin mówił o tym w jednym ze swoich wystąpień na temat czystej architektury.

Inną rzeczą do podkreślenia jest to, że uwaga candied_orange na temat braku cykli jest błędna. Tak, cyklicznie nie ma (i nie powinno) w architekturze kodu. Ale cykle między instancjami wykonawczymi są powszechne i często prowadzą do prostszego projektu.

Tak jest w przypadku MVVM. W MVVM View zależy od ViewModel, a ViewModel używa zdarzeń do powiadamiania View o swoich zmianach. Oznacza to, że przy projektowaniu klas istnieje tylko zależność od klas View do Model, ale podczas działania występuje cykliczna zależność między instancjami View i ViewModel. Z tego powodu program Orchestrator nie jest potrzebny, ponieważ ViewModel zapewni View sposób, aby dowiedzieć się, kiedy sam się zaktualizować. Dlatego „powiadomienia” na tym schemacie używają linii „squigly”, a nie linii bezpośredniej. Oznacza to, że View obserwuje zmiany w ViewModel, nie że ViewModel zależy od View.

wprowadź opis zdjęcia tutaj

Najważniejszą rzeczą, którą powinieneś wziąć z czystej architektury Martina, nie jest sam projekt, ale sposób radzenia sobie z zależnościami. Jednym z krytycznych punktów, które porusza w swoich przemówieniach, jest to, że gdy istnieje granica, wówczas wszystkie zależności kodu przekraczające tę granicę przekraczają ją w jednym kierunku. Na schemacie granica ta jest reprezentowana przez podwójną linię. I jest wiele zależności inwersji poprzez interfejsy ( InputBoundary, OutputBoundaryi DataAccessInterface), które ustala kierunek zależność kod.

Natomiast ViewModelw Czystej architekturze jest po prostu DTO bez logiki. Widać to po <DS>tagach. I to jest powód, dla którego orchestratorjest to konieczne, ponieważ Viewnie będzie wiedział, kiedy uruchomić jego logikę.

Gdybym miał „spłaszczyć” diagram tak, jak będzie on wyglądał w czasie wykonywania, będzie wyglądać tak:

wprowadź opis zdjęcia tutaj

Tak więc w czasie wykonywania zależności są w „złym” kierunku, ale to jest w porządku.

Polecam obejrzeć jego wykład o czystej architekturze, aby lepiej zrozumieć jego rozumowanie.

Euforyk
źródło
Twój „Orchestrator” nie powinien dzwonić do Prezentera. Czynnik wykorzystujący przypadek użycia robi to.
candied_orange
@candied_orange Prawda, to błąd.
Euforyczny
Dzięki za odpowiedź, zawsze dobrze jest uzyskać kilka różnych opinii. Głosowałem za odpowiedziami obu pań. Czy któryś z was wie, czy Robert Martin ma gdzieś bazę kodu, w której zaimplementował formę swojej architektury? Spojrzałem na jego projekt FitNess, ale nie widziałem lasu dla drzew. Czy mam rację spekulując, że mimo iż obraz, który zamieściłem, jest schematem, którego wujek Bob zawsze używa w swoich przemówieniach, to tak naprawdę jest to przykład tego, jak może wyglądać twoja architektura? Chociaż może wyglądać zupełnie inaczej, o ile przepływ zależności jest poprawny?
Fearnbuster
@Fearnbuster Na twoje ostatnie pytanie tak. Kierunek zależności jest ważniejszy niż struktura. Uważam, że „czysta architektura”, „architektura cebuli” i „architektura heksagonalna” są tak naprawdę implementacjami tego samego pomysłu „Kontroluj zależności”.
Euforyczny
@Euphoric Szczerze mówiąc, powiedziałbym, że prawdopodobnie tak jest, ponieważ na innym obrazie jego książki (ryc. 8.2 rozdziału 8) pokazuje architekturę, która wygląda inaczej. Na tym schemacie Kontroler jest pośrednikiem między Interaktorem a Prezenterem. Nie ma również granicy wyjściowej dla Interactor; wydaje się, że Interactor odbiera żądania przez interfejs, a następnie zwraca odpowiedzi przez ten sam interfejs (zakładam, że odbywa się to za pomocą prostej wartości zwracanej przez funkcję, ponieważ nie mogę wymyślić żadnego innego mechanizmu, który działałby w ten sposób).
Fearnbuster