MVVM jest pasmem pomocniczym dla źle zaprojektowanych warstw wiążących dane. W szczególności ma wiele zastosowań w świecie WPF / silverlight / WP7 z powodu ograniczeń w wiązaniu danych w WPF / XAML.
Odtąd zakładam, że mówimy o WPF / XAML, ponieważ to wyjaśni sprawę. Przyjrzyjmy się niektórym niedociągnięciom, które MVVM zamierza rozwiązać w WPF / XAML.
Kształt danych a kształt interfejsu użytkownika
„VM” w MVVM tworzy zestaw obiektów zdefiniowanych w C #, które odwzorowują na zestaw obiektów prezentacji zdefiniowanych w XAML. Te obiekty C # są zazwyczaj połączone z XAML za pośrednictwem właściwości DataContext na obiektach prezentacji.
W rezultacie wykres obiektu viewmodel musi zostać odwzorowany na wykresie obiektu prezentacji aplikacji. Nie oznacza to, że mapowanie musi być jeden do jednego, ale jeśli kontrolka listy jest zawarta w kontrolce okna, wówczas musi istnieć sposób na przejście z obiektu DataContext okna do obiektu, który opisuje dane tej listy.
Graf obiektowy viewmodel z powodzeniem oddziela wykres obiektowy modelu od grafu obiektowego interfejsu użytkownika, ale kosztem dodatkowej warstwy viewmodel, którą należy zbudować i utrzymywać.
Jeśli chcę przenieść niektóre dane z ekranu A na ekran B, muszę zadzierać z modelami viewmodels. Zdaniem biznesmena jest to zmiana interfejsu użytkownika. To powinno odbywać się wyłącznie w świecie XAML. Niestety rzadko. Co gorsza, w zależności od struktury modeli viewmode i tego, jak aktywnie zmieniają się dane, do wykonania tej zmiany może być konieczne przekierowanie danych.
Obejście nieefektywnego wiązania danych
Powiązania WPF / XAML są niewystarczająco ekspresyjne. Zasadniczo można uzyskać sposób dotarcia do obiektu, ścieżkę właściwości do przejścia i konwertery powiązań, aby dostosować wartość właściwości danych do wymagań obiektu prezentacji.
Jeśli musisz powiązać właściwość w języku C # z czymkolwiek bardziej złożonym, zasadniczo nie masz szczęścia. Nigdy nie widziałem aplikacji WPF bez konwertera powiązań, który zmieniłby true / false w Visible / Collapsed. Wiele aplikacji WPF ma również coś o nazwie NegatingVisibilityConverter lub podobny, który odwraca polaryzację. To powinno wywoływać dzwonki alarmowe.
MVVM zawiera wytyczne dotyczące strukturyzacji kodu C #, które można wykorzystać do złagodzenia tego ograniczenia. Możesz wystawić właściwość w swoim viewmodelu o nazwie SomeButtonVisibility i po prostu powiązać ją z widocznością tego przycisku. Twój XAML jest ładny i ładny teraz ... ale stałeś się urzędnikiem - teraz musisz ujawnić + aktualizację powiązań w dwóch miejscach (interfejs użytkownika i kod w języku C #), gdy twój interfejs ewoluuje. Jeśli potrzebujesz tego samego przycisku, aby znajdować się na innym ekranie, musisz udostępnić podobną właściwość w modelu widoku, do którego ten ekran może uzyskać dostęp. Co gorsza, nie mogę po prostu spojrzeć na XAML i zobaczyć, kiedy przycisk będzie już widoczny. Jak tylko powiązania staną się nieco nieistotne, muszę wykonać pracę detektywistyczną w kodzie C #.
Dostęp do danych jest agresywnie ograniczony
Ponieważ dane na ogół wchodzą do interfejsu użytkownika za pośrednictwem właściwości DataContext, trudno jest spójnie reprezentować dane globalne lub dane sesji w całej aplikacji.
Idea „aktualnie zalogowanego użytkownika” jest świetnym przykładem - często jest to naprawdę globalna sprawa w ramach jednej aplikacji. W WPF / XAML bardzo trudno jest zapewnić globalny dostęp do bieżącego użytkownika w spójny sposób.
Chciałbym swobodnie używać słowa „CurrentUser” w powiązaniach danych, aby odnosić się do aktualnie zalogowanego użytkownika. Zamiast tego muszę się upewnić, że każdy DataContext daje mi sposób na dotarcie do obiektu bieżącego użytkownika. MVVM może sobie z tym poradzić, ale modele widokowe będą bałaganem, ponieważ wszystkie muszą zapewniać dostęp do tych globalnych danych.
Przykład, w którym przewraca się MVVM
Powiedzmy, że mamy listę użytkowników. Obok każdego użytkownika chcemy wyświetlić przycisk „usuń użytkownika”, ale tylko wtedy, gdy zalogowanym użytkownikiem jest administrator. Ponadto użytkownicy nie mogą się usuwać.
Obiekty modelu nie powinny wiedzieć o aktualnie zalogowanym użytkowniku - będą po prostu reprezentować rekordy użytkownika w bazie danych, ale w jakiś sposób aktualnie zalogowany użytkownik musi być narażony na powiązania danych w wierszach listy. MVVM nakazuje utworzenie obiektu viewmodel dla każdego wiersza listy, który tworzy aktualnie zalogowanego użytkownika z użytkownikiem reprezentowanym przez ten wiersz listy, a następnie udostępnienie właściwości o nazwie „DeleteButtonVisibility” lub „CanDelete” na tym obiekcie viewmodel (w zależności od twoich odczuć o wiązaniu konwerterów).
Ten obiekt będzie wyglądał okropnie podobnie do obiektu użytkownika pod wieloma innymi względami - może być konieczne odzwierciedlenie wszystkich właściwości obiektu modelu użytkownika i przekazywanie aktualizacji tych danych w miarę jego zmian. Wydaje się to bardzo niegrzeczne - ponownie, MVVM czyni cię urzędnikiem, zmuszając cię do utrzymania tego obiektu podobnego do użytkownika.
Zastanów się - prawdopodobnie musisz również reprezentować właściwości użytkownika w bazie danych, modelu i widoku. Jeśli masz interfejs API między tobą a bazą danych, jest gorzej - są one reprezentowane w bazie danych, serwerze API, kliencie API, modelu i widoku. Byłbym bardzo niezdecydowany, aby przyjąć wzór projektowy, który dodał kolejną warstwę, którą należy dotknąć za każdym razem, gdy właściwość jest dodawana lub zmieniana.
Co gorsza, ta warstwa skaluje się ze złożonością interfejsu użytkownika, a nie ze złożonością modelu danych. Często te same dane są reprezentowane w wielu miejscach i w interfejsie użytkownika - to nie tylko dodaje warstwę, ale dodaje warstwę o dużej dodatkowej powierzchni!
Jak mogło być
W przypadku opisanym powyżej chciałbym powiedzieć:
<Button Visibility="{CurrentUser.IsAdmin && CurrentUser.Id != Id}" ... />
CurrentUser będzie globalnie narażony na wszystkie XAML w mojej aplikacji. Id odwoływałby się do właściwości w DataContext dla mojego wiersza listy. Widoczność zmieni się automatycznie z logicznej. Wszelkie aktualizacje Id, CurrentUser.IsAdmin, CurrentUser lub CurrentUser.Id spowodowałyby aktualizację widoczności tego przycisku. Bułka z masłem.
Zamiast tego WPF / XAML zmusza użytkowników do stworzenia kompletnego bałaganu. O ile wiem, niektórzy kreatywni blogerzy podali nazwę temu bałaganowi, a ta nazwa to MVVM. Nie daj się zwieść - nie należy do tej samej klasy co wzorce projektowe GoF. To brzydki hack do obejścia brzydkiego systemu wiązania danych.
(Takie podejście jest czasem nazywane „programowaniem reaktywnym”, na wypadek, gdybyś chciał przeczytać więcej).
Podsumowując
Jeśli musisz pracować w WPF / XAML, nadal nie polecam MVVM.
Chcesz, aby Twój kod był tak skonstruowany, jak w powyższym przykładzie „jak mogło być”. Model byłby wystawiony bezpośrednio na widok, ze złożonymi wyrażeniami powiązania danych + elastycznymi koercjami wartości. Jest o wiele lepszy - bardziej czytelny, zapisywalny i łatwiejszy w utrzymaniu.
MVVM mówi, abyś ustrukturyzował swój kod w bardziej szczegółowy, mniej konserwowalny sposób.
Zamiast MVVM, stwórz kilka rzeczy, które pomogą ci przybliżyć dobre wrażenia: Opracuj konwencję konsekwentnego wyświetlania stanu globalnego w interfejsie użytkownika. Zbuduj sobie narzędzia z konwerterów wiązania, MultiBinding itp., Które pozwalają wyrazić bardziej złożone wyrażenia wiązania. Zbuduj sobie bibliotekę wiążących konwerterów, aby sprawić, że częste przypadki przymusu będą mniej bolesne.
Jeszcze lepiej - zamień XAML na coś bardziej wyrazistego. XAML to bardzo prosty format XML do tworzenia instancji obiektów C # - nie byłoby trudno wymyślić bardziej wyrazisty wariant.
Moje inne zalecenie: nie używaj zestawów narzędzi, które wymuszają tego rodzaju kompromisy. Zaszkodzą jakości twojego produktu końcowego, popychając cię do bzdur jak MVVM, zamiast skupiać się na domenie problemowej.