Czy MVVM nie ma sensu? [Zamknięte]

91

Czy ortodoksyjne wdrażanie MVVM jest bezcelowe? Tworzę nową aplikację i rozważałem Windows Forms i WPF. Wybrałem WPF, ponieważ jest przyszłościowy i zapewnia dużą elastyczność. Jest mniej kodu i łatwiej jest wprowadzać istotne zmiany w interfejsie użytkownika przy użyciu języka XAML.

Ponieważ wybór WPF jest oczywisty, doszedłem do wniosku, że równie dobrze mogę przejść całą drogę, używając MVVM jako mojej architektury aplikacji, ponieważ oferuje ona mieszalność, problemy z separacją i testowalność jednostkową. Teoretycznie wydaje się piękny jak Święty Graal programowania interfejsu użytkownika. Ta krótka przygoda; jednak zamienił się w prawdziwy ból głowy. Zgodnie z oczekiwaniami w praktyce stwierdzam, że zamieniłem jeden problem na inny. Mam tendencję do bycia programistą obsesyjnym, ponieważ chcę robić rzeczy we właściwy sposób, aby uzyskać właściwe wyniki i być może zostać lepszym programistą. Wzorzec MVVM właśnie oblał mój test produktywności i właśnie zmienił się w wielki ohydny hack!

Jasnym przykładem jest dodanie obsługi modalnego okna dialogowego. Prawidłowym sposobem jest wyświetlenie okna dialogowego i powiązanie go z modelem widoku. Uruchomienie tego jest trudne. Aby skorzystać z wzorca MVVM, musisz rozprowadzić kod w kilku miejscach w warstwach aplikacji. Musisz także używać ezoterycznych konstrukcji programistycznych, takich jak szablony i wyrażenia lamba. Rzeczy, które sprawiają, że gapisz się na ekran drapiąc się po głowie. To sprawia, że ​​konserwacja i debugowanie są koszmarem czekającym, jak niedawno odkryłem. Miałem okno około działające dobrze, dopóki nie dostałem wyjątku przy drugim wywołaniu, mówiąc, że nie może ponownie wyświetlić okna dialogowego po zamknięciu. Musiałem dodać procedurę obsługi zdarzeń dla funkcji zamykania okna dialogowego, inny w implementacji IDialogView i wreszcie inny w IDialogViewModel. Myślałem, że MVVM uratuje nas przed tak ekstrawaganckim hakowaniem!

Jest kilka osób z konkurencyjnymi rozwiązaniami tego problemu i wszystkie są hackami i nie zapewniają czystego, łatwego do ponownego użycia, eleganckiego rozwiązania. Większość zestawów narzędzi MVVM prześwietla okna dialogowe, a kiedy je rozwiązują, są po prostu oknami ostrzegawczymi, które nie wymagają niestandardowych interfejsów ani modeli widoku.

Planuję zrezygnować z wzorca widoku MVVM, a przynajmniej jego ortodoksyjnej implementacji. Co myślisz? Czy warto było, gdybyś miał jakieś kłopoty? Czy jestem tylko niekompetentnym programistą, czy też MVVM nie jest tym, czym ma być?

Joel Rodgers
źródło
8
Zawsze wątpiłem, czy MVVM jest nadmierną inżynierią. Interesujące pytanie.
Taylor Leese
12
Wzorce, takie jak MVVM i MVC, wydają się być nadmierną inżynierią, dopóki nie musisz wprowadzić pewnych modyfikacji lub wymienić komponent. Za pierwszym razem cała ceremonia się opłaca.
Robert Harvey
42
Lambdy są ezoteryczne? nowości do mnie.
Ray Booysen
6
@Ray - Haha, +1 za ten komentarz! : D
Venemo
8
Jak zauważył Alan Cooper ponad dziesięć lat temu w About Face , jeśli projektujesz interfejsy użytkownika, a okna dialogowe modalne nie są przypadkiem skrajnym, prawdopodobnie robisz coś nie tak.
Robert Rossney

Odpowiedzi:

61

Przepraszam, jeśli moja odpowiedź stała się trochę dłuższa, ale nie wiń mnie! Twoje pytanie również jest długie.

Podsumowując, MVVM nie jest bezcelowe.

Jasnym przykładem jest dodanie obsługi modalnego okna dialogowego. Prawidłowym sposobem jest wyświetlenie okna dialogowego i powiązanie go z modelem widoku. Uruchomienie tego jest trudne.

Tak, naprawdę jest.
Jednak MVVM zapewnia sposób oddzielenia wyglądu interfejsu użytkownika od jego logiki. Nikt nie zmusza cię do używania go wszędzie i nikt nie trzyma pistoletu przy twoim czole, aby stworzyć osobny ViewModel do wszystkiego.

Oto moje rozwiązanie dla tego konkretnego przykładu:
Sposób, w jaki interfejs użytkownika obsługuje określone dane wejściowe, nie należy do firmy ViewModel. Dodałbym kod do pliku .xaml.cs widoku, który tworzy wystąpienie okna dialogowego i ustawia tę samą instancję ViewModel (lub coś innego, jeśli to konieczne) jako jego DataContext.

Aby skorzystać z wzorca MVVM, musisz rozprowadzić kod w kilku miejscach w warstwach aplikacji. Musisz także używać ezoterycznych konstrukcji programistycznych, takich jak szablony i wyrażenia lamba.

Cóż, nie musisz go używać w kilku miejscach. Oto jak bym to rozwiązał:

  • Dodaj kod XAML do widoku i nic w pliku xaml.cs
  • Napisz logikę każdej aplikacji (z wyjątkiem rzeczy, które bezpośrednio działałyby z elementami interfejsu użytkownika) w ViewModel
  • Cały kod, który powinien być wykonany przez interfejs użytkownika, ale nie ma nic wspólnego z logiką biznesową, trafia do plików .xaml.cs

Myślę, że celem MVVM jest przede wszystkim oddzielenie logiki aplikacji od konkretnego UI, co pozwala na łatwe modyfikacje (lub całkowitą wymianę) UI.
Używam następującej zasady: ViewModel może wiedzieć i założyć wszystko, czego chce, z ViewModel, ale ViewModel nie może NIC wiedzieć o widoku.
WPF zapewnia ładny model powiązań, którego można użyć, aby to osiągnąć.

(Przy okazji, szablony i wyrażenia lambda nie są ezoteryczne, jeśli są prawidłowo używane. Ale jeśli nie chcesz, nie używaj ich).

Rzeczy, które sprawiają, że gapisz się na ekran, drapiąc się po głowie.

Tak, znam to uczucie. Dokładnie to, co czułem, kiedy po raz pierwszy zobaczyłem MVVM. Ale kiedy już to zrozumiesz, nie będzie już źle.

Miałem o pudełku działającym dobrze ...

Dlaczego miałbyś umieścić ViewModel za oknem na temat? Nie ma w tym sensu.

Większość zestawów narzędzi MVVM prześwietla okna dialogowe, a kiedy je rozwiązują, są tylko oknami ostrzegawczymi, które nie wymagają niestandardowych interfejsów ani modeli widoku.

Tak, ponieważ sam fakt, że element UI znajduje się w tym samym oknie lub w innym oknie, albo że orbituje obecnie wokół Marsa, nie stanowi problemu dla ViewModels.
Rozdzielenie problemów

EDYTOWAĆ:

Oto bardzo fajny film, którego tytuł brzmi: Zbuduj swój własny framework MVVM . Warto obejrzeć.

Venemo
źródło
3
+1 za ostatnie trzy słowa. Ale reszta odpowiedzi też jest dobra. :)
Robert Harvey
13
+1 za poradę dotyczącą używania kodu. Powszechnym błędem jest przekonanie, że „złe” jest używanie kodu źródłowego w MVVM ... ale w przypadku czysto związanych z interfejsem użytkownika jest to właściwy sposób.
Thomas Levesque
1
@Thomas: Tak, nie mogłem się bardziej zgodzić. Widziałem kilka implementacji, w których ludzie umieszczali cały kod (nawet związany z interfejsem użytkownika) w ViewModel, ponieważ (według nich) „tam jest kod”. To było całkiem niezłe.
Venemo
4
@Venemo, myślę, że możesz zawrzeć wiele rzeczy, które chciałbyś umieścić w kodzie, używając technik takich jak niestandardowe zachowania, co jest przydatne, jeśli wielokrotnie piszesz kod kleju. Ogólnie jednak uważam, że lepiej jest użyć kodu związanego z klejem niż zhakować niezręczny XAML. Moim zdaniem głównym problemem jest upewnienie się, że w kodzie nie ma nic na tyle wyrafinowanego, by uzasadnić testy jednostkowe. Wszystko, co jest wystarczająco złożone, jest lepiej hermetyzowane w ViewModel lub klasie rozszerzającej, takiej jak Behavior lub MarkupExtension.
Dan Bryant
8
@ Thomas: Masz rację, największym mitem na temat MVVM jest to, że celem MVVM jest pozbycie się kodu źródłowego. Celem jest usunięcie kodu Non-UI z kodu. Umieszczenie kodu tylko dla interfejsu użytkownika w ViewModel jest tak samo złe, jak umieszczenie kodu domeny powodującej problem w kodzie.
Jim Reineri
8

Uruchomienie tego jest trudne. Aby skorzystać z wzorca MVVM, musisz rozprowadzić kod w kilku miejscach w warstwach aplikacji. Musisz także używać ezoterycznych konstrukcji programistycznych, takich jak szablony i wyrażenia lamba.

Dla zwykłego modalnego okna dialogowego? Z pewnością robisz tam coś złego - implementacja MVVM nie musi być tak skomplikowana.

Biorąc pod uwagę, że jesteś nowy zarówno w MVVM, jak i WPF, prawdopodobnie wszędzie używasz nieoptymalnych rozwiązań i niepotrzebnie komplikujesz rzeczy - przynajmniej zrobiłem to, kiedy po raz pierwszy przeszedłem na WPF. Przed poddaniem się upewnij się, że problem dotyczy naprawdę MVVM, a nie implementacji.

MVVM, MVC, Document-View itp. To stara rodzina wzorców. Są wady, ale nie ma fatalnych usterek tego rodzaju, które opisujesz.

ima
źródło
5

Jestem w trakcie dość złożonego rozwoju MVVM przy użyciu PRISM, więc już musiałem sobie radzić z tego rodzaju problemami.

Moje osobiste wnioski:

MVVM vs MVC / PopUps & co

  • MVVM to naprawdę świetny wzorzec iw większości przypadków całkowicie zastępuje MVC dzięki potężnemu powiązaniu danych w WPF
  • W większości przypadków dzwonienie do warstwy usług bezpośrednio od prezentera jest legalną implementacją
  • Nawet dość złożone scenariusze List / Detail mogą być implementowane przez czysty MVVM dzięki składni {Binding Path = /}
  • Niemniej jednak, gdy trzeba wdrożyć złożoną koordynację między wieloma widokami, kontroler jest obowiązkowy
  • Można wykorzystać wydarzenia; stary wzorzec, który implikuje przechowywanie instancji IView (lub AbstractObserver) w kontrolerze, jest przestarzały
  • Kontroler można umieścić w każdym Presenter za pomocą kontenera IOC
  • Usługa IEventAggregator Prism jest kolejnym możliwym rozwiązaniem, jeśli jedynym zastosowaniem kontrolera jest wysyłanie zdarzeń (w tym przypadku może całkowicie zastąpić kontroler)
  • Jeśli widoki mają być tworzone dynamicznie, jest to bardzo dobrze dopasowane zadanie dla kontrolera (w pryzmacie do kontrolera zostanie wstrzyknięty (IOC) IRegionManager)
  • Modalne okna dialogowe są przeważnie przestarzałe w nowoczesnych aplikacjach złożonych, z wyjątkiem naprawdę blokujących operacji, takich jak obowiązkowe potwierdzenia; w takich przypadkach aktywacja modalna może być wyabstrahowana jako usługa wywoływana wewnątrz kontrolera i zaimplementowana przez wyspecjalizowaną klasę, która umożliwia również zaawansowane testowanie jednostkowe na poziomie prezentacji. Na przykład kontroler wywoła IConfirmationService.RequestConfirmation („czy na pewno”), co spowoduje wyświetlenie modalnego okna dialogowego w czasie wykonywania i może być łatwo wyszydzane podczas testów jednostkowych
dan ionescu
źródło
5

Problem dialogów rozwiązuję oszukując. My MainWindow implementuje interfejs IWindowServices, który uwidacznia wszystkie okna dialogowe specyficzne dla aplikacji. Moje inne ViewModels mogą następnie zaimportować interfejs usług (używam MEF, ale możesz łatwo ręcznie przekazać interfejs przez konstruktory) i użyć go do wykonania tego, co jest konieczne. Na przykład, oto jak wygląda interfejs dla mojej małej aplikacji użytkowej:

//Wrapper interface for dialog functionality to allow for mocking during tests
public interface IWindowServices
{
    bool ExecuteNewProject(NewProjectViewModel model);

    bool ExecuteImportSymbols(ImportSymbolsViewModel model);

    bool ExecuteOpenDialog(OpenFileDialog dialog);

    bool ExecuteSaveDialog(SaveFileDialog dialog);

    bool ExecuteWarningConfirmation(string text, string caption);

    void ExitApplication();
}

To umieszcza wszystkie wykonania Dialog w jednym miejscu i można je łatwo usunąć do testów jednostkowych. Podążam za wzorcem, który klient okna dialogowego musi utworzyć odpowiedni ViewModel, który może następnie skonfigurować w razie potrzeby. Bloki wywołań Execute, a następnie klient może przejrzeć zawartość ViewModel, aby zobaczyć wyniki Dialog.

Bardziej `` czysty '' projekt MVVM może być ważny w przypadku dużych aplikacji, gdzie potrzebna jest czystsza izolacja i bardziej złożony skład, ale w przypadku małych i średnich aplikacji myślę, że praktyczne podejście, z odpowiednimi usługami do wyeksponowania wymaganych haków, jest wystarczające .

Dan Bryant
źródło
Ale gdzie to nazywasz? Czy nie byłoby lepiej po prostu skonstruować okno dialogowe w funkcjach klasy IWindowServices, zamiast je przekazywać. W ten sposób wywołujący widok modelu nie musiałby nic wiedzieć o konkretnej implementacji okna dialogowego.
Joel Rodgers
Interfejs jest wstrzykiwany do każdej instancji mojego modelu ViewModel, która wymaga dostępu do okien dialogowych aplikacji. W większości przypadków przekazuję ViewModel dla okna dialogowego, ale trochę się rozleniwiłem i używam WPF OpenFileDialog i SaveFileDialog do wywołań okna dialogowego pliku. Moim głównym celem była izolacja na potrzeby testów jednostkowych, więc to wystarczy do tego celu. Gdybyś chciał lepszej izolacji, prawdopodobnie chciałbyś utworzyć OpenFileViewModel i SaveFileViewModel, które powielałyby niezbędne właściwości okien dialogowych.
Dan Bryant
Zauważ, że zdecydowanie nie jest to czyste podejście, ponieważ ViewModel używający okien dialogowych wie o konkretnym ViewModel dla każdego okna dialogowego, które chce otworzyć. Wydaje mi się, że jest to dość czyste, ale zawsze można dodać dodatkową warstwę izolacji z klasą, która czysto eksponuje parametry wymagane do użycia okna dialogowego, ukrywając niepotrzebną widoczność właściwości ViewModel używanych podczas wiązania. W przypadku mniejszych zastosowań uważam, że ta dodatkowa izolacja to przesada.
Dan Bryant
5

Wzorce projektowe mają Ci pomóc, a nie przeszkadzać. Niewielka część bycia dobrym programistą polega na tym, aby wiedzieć, kiedy „łamać zasady”. Jeśli MVVM jest uciążliwe dla zadania i ustaliłeś, że przyszła wartość nie jest warta wysiłku, nie używaj wzorca. Na przykład, jak skomentowały inne plakaty, dlaczego miałbyś przechodzić przez wszystkie koszty ogólne, aby wdrożyć proste pudełko na temat?

Wzorce projektowe nigdy nie miały być dogmatycznie przestrzegane.

RMart
źródło
2
Poprawny. Prosty opis powinien być prosty, ale co zrobić, jeśli musisz wyświetlić informacje, takie jak wersja, licencja, uruchomiony proces, nazwa firmy itp. To są wszystkie informacje, które gdzieś mieszkają. W standardowych formularzach możesz po prostu powiązać wszystkie te informacje i skończyć z nimi. MVVM mówi, że powinieneś utworzyć dla niego model widoku, który jest zbędny.
ATL_DEV,
1

Jak sam wzór, MVVM jest świetny. Ale biblioteka kontrolna WPF dostarczana z obsługą wiązania danych NET 4.0 jest bardzo ograniczona, jest o wiele lepsza niż WinForm, ale nadal nie jest wystarczająca dla MVVM z możliwością wiązania, powiedziałbym, że jej moc wynosi około 30% tego, co jest potrzebne do powiązania MVVM.
Bindable MVVM: to interfejs użytkownika, w którym ViewModel jest połączony z widokiem tylko przy użyciu powiązania danych.
Wzorzec MVVM dotyczy reprezentacji obiektu ViewState, nie opisuje sposobu utrzymywania synchronizacji między View i ViewModel, w WPF jest to powiązanie danych, ale może to być wszystko. I faktycznie możesz użyć wzorca MVVM w dowolnym zestawie narzędzi UI, który obsługuje zdarzenia \ wywołania zwrotne, możesz go użyć w czystym WinAPI w WinForms (tak zrobiłem i nie jest to dużo więcej pracy ze zdarzeniami \ callbackami), a możesz nawet użyć go w tekście Konsola, podobnie jak przepisywanie programu Norton Commander w DoS przy użyciu wzorca MVVM.

Krótko mówiąc: MVVM nie jest bezcelowe, jest świetne. Biblioteka formantów .NET 4.0 WPF to kosz.

Oto prosty dowód słuszności koncepcji ViewModel, którego nie można powiązać z danymi w czysty sposób MVVM przy użyciu WPF.

public class PersonsViewModel
{
    public IList<Person> PersonList;
    public IList<ColumnDescription> TableColumns;
    public IList<Person> SelectedPersons;
    public Person ActivePerson;
    public ColumnDescription SortedColumn;
}

Nie możesz powiązać danych nagłówków kolumn DataGrid WPF, nie możesz powiązać danych z wybranymi wierszami itp. Itp., Zrobisz to w prosty sposób kodem lub napiszesz 200 wierszy kodu hakerskiego XAML dla tych 5 wierszy najprostszego ViewModel. Możesz sobie tylko wyobrazić, jak sytuacja się pogarsza ze złożonymi ViewModels.
Więc odpowiedź jest prosta, chyba że piszesz aplikację Hello World, używanie wiążącego się MVVM w WPF nie ma sensu. Większość czasu spędzisz na myśleniu o hackowaniu, aby powiązać Cię z ViewModelem. Wiązanie danych jest przyjemne, ale przygotuj się na powrót do 70% czasu zdarzenia.

Alex Burtsev
źródło
Możesz to powiązać, z konwerterami, do DataGrid.
Cameron MacFarland
@CameronMacFarland: Nie wszystkie, niektóre właściwości są tylko do odczytu i nie można ich powiązać, niektóre po prostu nie istnieją i są tylko zdarzenia, które zgłaszają zmianę stanu.
Alex Burtsev
Przyznaję, że nie mam dużego doświadczenia w korzystaniu z WPF DataGrid. Unikam tego, ponieważ jest brzydki i nie pasuje już do WPF. Powiedziawszy, że połączenie konwerterów i AttachedProperties do obsługi zdarzeń powinno zapewnić Ci to, czego potrzebujesz.
Cameron MacFarland
1
Alex, problemy, które masz, dotyczą projektu DataGrid, a nie MVVM. Po prostu niepoprawne jest stwierdzenie, że „Powiązanie danych jest ładne, ale przygotuj się na powrót do 70% czasu zdarzenia”. Napisałem kilka obiektywnie dużych aplikacji WPF, w których nie ma żadnych programów obsługi zdarzeń w interfejsie użytkownika - z wyjątkiem programu obsługi zdarzeń, którego siatka danych (Telerik) potrzebuje do inicjalizacji.
Robert Rossney,
3
Myślę, że możesz odnieść większy sukces, jeśli zamiast przyjąć postawę „To jest źle zaprojektowane i nie działa”, próbowałeś: „Dlaczego to działa dla innych ludzi, a nie dla mnie?”. Może się okazać, że powodem, dla którego coś jest trudne, jest to, że nie wiesz jeszcze, jak to zrobić.
Robert Rossney,
0

Nie, to nie jest bezcelowe, ale trudno jest owinąć głowę, mimo że sam wzór jest absurdalnie prosty. Istnieje mnóstwo dezinformacji i różnych grup, które walczą o właściwy sposób. Myślę, że w przypadku WPF i Silverlight powinieneś użyć MVVM albo będziesz zbytnio kodować i próbować rozwiązać problemy w nowym modelu „starej” metodologii wygrywających formularzy, która po prostu prowadzi do kłopotów. Jest to bardziej przypadek w Silverlight, ponieważ wszystko musi być asynchroniczne (możliwe są hacki wokół tego, ale powinieneś po prostu wybrać inną platformę).

Sugeruję przeczytanie tego artykułu Upraszczanie widoku drzewa WPF przy użyciu wzorca ViewModel uważnie, aby zobaczyć, jak można dobrze zaimplementować MVVM i umożliwić zmianę mentalności form wygrywających na nowy sposób myślenia w MVVM. W skrócie, jeśli chcesz coś zrobić, najpierw zastosuj logikę do ViewModel, a nie do View. Chcesz wybrać przedmiot? Zmienić ikonę? Nie iteruj po elementach interfejsu użytkownika, po prostu zaktualizuj właściwości modeli i pozwól, aby powiązanie danych załatwiło sprawę.

HolownikKapitan
źródło
-1

Widziałem ten sam problem z wieloma implementacjami MVVM, jeśli chodzi o (modalne) okna dialogowe. Kiedy patrzę na uczestników wzorca MVVM, to mam wrażenie, że czegoś brakuje do zbudowania spójnej aplikacji.

  • Widok zawiera określone kontrolki GUI i definiuje wygląd interfejsu użytkownika.
  • ViewModel reprezentuje stan i zachowanie prezentacji.
  • Model może być obiektem biznesowym z warstwy domeny lub usługą, która dostarcza niezbędne dane.

Ale brakuje:

  • Kto tworzy ViewModels?
  • Kto jest odpowiedzialny za obieg aplikacji?
  • Kto pośredniczy między ViewModels, kiedy muszą się ze sobą komunikować?

Moje podejście polega na wprowadzeniu kontrolera (przypadku użycia), który jest odpowiedzialny za brakujące punkty. Jak to działa, można zobaczyć w przykładowych aplikacjach WPF Application Framework (WAF) .

jbe
źródło
Wzorzec Mediator zaimplementowany przez Josha Smitha rozwiązał wszystkie moje problemy z komunikacją View Model. Messenger.NotifyColleagues zapewnił sposób na posiadanie całkowicie niezależnych modeli widoku, które wiedziały, jak reagować na globalne wydarzenia (jeśli ich to obchodzi), bez konieczności posiadania dwóch modeli widoku, którzy wiedzą o sobie. Już kilka razy uratował nasz boczek.
JasonD