MVVM i wzorzec usługi

14

Buduję aplikację WPF przy użyciu wzorca MVVM. W tej chwili moje viewmodels wywołuje warstwę usługi, aby pobrać modele (co nie ma znaczenia dla viewmodel) i przekonwertować je na viewmodels. Używam iniekcji konstruktora, aby przekazać wymaganą usługę do modelu viewmodel.

Jest łatwy do przetestowania i działa dobrze w modelach view z kilkoma zależnościami, ale gdy tylko próbuję utworzyć viewModels dla złożonych modeli, mam konstruktora z dużą ilością usług wstrzykniętych do niego (jedna do pobierania każdej zależności i listy wszystkich dostępnych wartości powiązanie na przykład z itemSource). Zastanawiam się, jak obsługiwać wiele takich usług i nadal mam model widoku, który mogę z łatwością testować jednostkowo.

Mam na myśli kilka rozwiązań:

  1. Tworzenie singletonu usług (IServices) zawierającego wszystkie dostępne usługi jako interfejsy. Przykład: Services.Current.XXXService.Retrieve (), Services.Current.YYYService.Retrieve (). W ten sposób nie mam wielkiego konstruktora z mnóstwem parametrów usług.

  2. Tworzenie fasady dla usług używanych przez viewModel i przekazywanie tego obiektu w ctor mojego viewmodela. Ale potem będę musiał stworzyć fasadę dla każdego z moich złożonych modeli widoków i może to być trochę dużo ...

Jak myślisz, jaki jest „właściwy” sposób wdrożenia tego rodzaju architektury?

alfa-alfa
źródło
Myślę, że „właściwym” sposobem na to jest utworzenie osobnej warstwy, która wywołuje usługi i wykonuje wszelkie rzuty niezbędne do stworzenia ViewModel. Twoje ViewModels nie powinny być odpowiedzialne za ich tworzenie.
Amy Blankenship
@AmyBlankenship: Modele widoków nie powinny (lub nawet muszą być w stanie) tworzyć same, ale nieuchronnie czasami będą odpowiedzialne za tworzenie innych modeli widoków . Kontener IoC z automatycznym wsparciem fabrycznym jest tutaj ogromną pomocą.
Aaronaught
„Czasami” i „powinien” to dwa różne zwierzęta;)
Amy Blankenship
@AmyBlankenship: Czy sugerujesz, że modele widoków nie powinny tworzyć innych modeli widoków? To trudna pigułka do przełknięcia. Rozumiem, że modeli widoków nie należy używać newdo tworzenia innych modeli widoków, ale myślę o czymś tak prostym jak aplikacja MDI, w której kliknięcie przycisku lub menu „nowego dokumentu” doda nową kartę lub otworzy nowe okno. Powłoka / przewodnik musi być w stanie tworzyć nowe wystąpienia czegoś , nawet jeśli jest ukryty za jedną lub kilkoma warstwami pośredniczącymi.
Aaronaught
Cóż, z pewnością musi mieć możliwość zażądania wykonania gdzieś Widoku. Ale zrobić to sam? Nie w moim świecie :). Ale z drugiej strony w świecie, w którym mieszkam, nazywamy VM „Modelem prezentacji”.
Amy Blankenship,

Odpowiedzi:

22

W rzeczywistości oba te rozwiązania są złe.

Tworzenie singletonu usług (IServices) zawierającego wszystkie dostępne usługi jako interfejsy. Przykład: Services.Current.XXXService.Retrieve (), Services.Current.YYYService.Retrieve (). W ten sposób nie mam wielkiego konstruktora z mnóstwem parametrów usług.

Zasadniczo jest to wzorzec lokalizatora usług , który jest anty-wzorcem. Jeśli to zrobisz, nie będziesz już w stanie zrozumieć, na czym tak naprawdę zależy model widoku, nie patrząc na jego prywatną implementację, co bardzo utrudni testowanie lub refaktoryzację.

Tworzenie fasady dla usług używanych przez viewModel i przekazywanie tego obiektu w ctor mojego viewmodela. Ale potem będę musiał stworzyć fasadę dla każdego z moich złożonych modeli widoków i może to być trochę dużo ...

To nie jest tak bardzo anty-wzór, ale zapach kodu. Zasadniczo tworzysz obiekt parametru , ale celem wzorca refaktoryzacji PO jest zajmowanie się zestawami parametrów, które są używane często i w wielu różnych miejscach , podczas gdy ten parametr byłby użyty tylko raz. Jak wspomniałeś, spowodowałoby to wiele rozdętych kodów bez żadnej realnej korzyści i nie grałoby dobrze z wieloma kontenerami IoC.

W rzeczywistości obie powyższe strategie pomijają ogólny problem, którym jest zbyt duże sprzężenie między modelami widoków a usługami . Po prostu ukrycie tych zależności w lokalizatorze usług lub obiekcie parametru w rzeczywistości nie zmienia liczby innych obiektów, od których zależy model widoku.

Pomyśl, jak przetestowałbyś jeden z tych modeli widoków. Jak duży będzie twój kod instalacyjny? Ile rzeczy trzeba zainicjować, aby działało?

Wiele osób zaczynających od MVVM próbuje stworzyć modele widoków dla całego ekranu , co jest zasadniczo niewłaściwym podejściem. MVVM polega na kompozycji , a ekran z wieloma funkcjami powinien składać się z kilku różnych modeli widoków, z których każdy zależy tylko od jednego lub kilku modeli / usług wewnętrznych. Jeśli muszą się ze sobą komunikować, robisz to za pośrednictwem pub / sub (broker komunikatów, szyna zdarzeń itp.)

To, co naprawdę musisz zrobić, to przefakturować modele widoków, aby miały mniej zależności . Następnie, jeśli potrzebujesz zagregowanego „ekranu”, tworzysz inny model widoku, aby agregować mniejsze modele widoku. Ten zagregowany model widoku sam w sobie nie musi wiele robić, więc z kolei jest dość łatwy do zrozumienia i przetestowania.

Jeśli zrobiłeś to poprawnie, powinno być oczywiste po prostu patrząc na kod, ponieważ będziesz miał krótkie, zwięzłe, specyficzne i testowalne modele widoku.

Aaronaught
źródło
Tak, prawdopodobnie to właśnie skończę! Wielkie dzięki, proszę pana.
alfa-alfa,
Cóż, już zakładałem, że już tego próbował, ale mu się nie udało. @ alfa-alfa
Euphoric
@Euphoric: Jak ci się to nie udaje? Jak powiedziałby Yoda: rób lub nie, nie ma próby.
Aaronaught
@Aaronaught Na przykład naprawdę potrzebuje wszystkich danych w jednym modelu viewmodel. Może ma siatkę i różne kolumny pochodzą z różnych usług. Nie możesz tego zrobić z kompozycją.
Euforia
@Euphoric: W rzeczywistości można to rozwiązać za pomocą kompozycji, ale można to zrobić poniżej poziomu modelu widoku. To po prostu kwestia stworzenia odpowiednich abstrakcji. W takim przypadku potrzebna jest tylko jedna usługa do obsługi początkowego zapytania, aby uzyskać listę identyfikatorów oraz sekwencję / listę / tablicę „elementów wzbogacających”, które opisują własne informacje. Stwórz siatkę jako swój własny model widoku, a problem został rozwiązany za pomocą dwóch zależności, a testowanie jest niezwykle łatwe.
Aaronaught
1

Mógłbym napisać o tym książkę ... tak naprawdę jestem;)

Po pierwsze, nie ma uniwersalnego „właściwego” sposobu robienia rzeczy. Musisz wziąć pod uwagę inne czynniki.

Możliwe, że Twoje usługi są zbyt szczegółowe. Lepszym rozwiązaniem może być owijanie Usług fasadami zapewniającymi interfejs, z którego korzystałby konkretny model Viewmodel lub nawet klaster pokrewnych modeli ViewModels.

Jeszcze prostsze byłoby zawinięcie usług w jedną fasadę, z której korzystają wszystkie modele widoków. Oczywiście może to być bardzo duży interfejs z wieloma niepotrzebnymi funkcjami dla przeciętnego scenariusza. Ale powiedziałbym, że nie różni się niczym od routera wiadomości, który obsługuje każdą wiadomość w twoim systemie.

W rzeczywistości to, co widziałem, że wiele architektur ostatecznie ewoluowało, to szyna komunikatów zbudowana wokół czegoś takiego jak wzorzec Event Aggregator. Testowanie jest tam łatwe, ponieważ klasa testowa rejestruje tylko słuchacz w EA i odpala odpowiednie zdarzenie w odpowiedzi. Ale jest to zaawansowany scenariusz, do którego rozwoju potrzeba czasu. Mówię: zacznij od jednoczącej fasady i stamtąd.

Michael Brown
źródło
Ogromna fasada usług różni się bardzo od brokera wiadomości. Jest prawie na drugim końcu spektrum zależności. Cechą charakterystyczną tej architektury jest to, że nadawca nie wie nic o odbiorniku i może istnieć wiele odbiorników (pub / sub lub multicast). Być może mylisz to z „zdalnym” sposobem RPC, który po prostu ujawnia tradycyjną usługę za pośrednictwem zdalnego protokołu, a nadawca jest nadal sprzężony z odbiorcą, zarówno fizycznie (adres punktu końcowego), jak i logicznie (wartość zwracana).
Aaronaught
Podobieństwo polega na tym, że fasada działa jak router, osoba dzwoniąca nie wie, która usługa / usługi obsługuje połączenie, tak jak klient wysyłający wiadomość nie wie, kto ją obsługuje.
Michael Brown,
Tak, ale fasada jest więc Bogiem . Ma wszystkie zależności, które mają modele widoków, prawdopodobnie więcej, ponieważ będą udostępniane przez kilka. W efekcie wyeliminowałeś zalety luźnego łączenia, nad którym tak ciężko pracowałeś, ponieważ teraz, gdy coś dotyka mega-fasady, nie masz pojęcia, od której funkcjonalności zależy. Obraz piszący test jednostkowy dla klasy korzystającej z fasady. Tworzysz próbę dla fasady. Jakie metody wyśmiewasz? Jak wygląda Twój kod instalacyjny?
Aaronaught
Różni się to bardzo od brokera komunikatów, ponieważ broker również nie wie nic o implementacji procedur obsługi komunikatów . Wykorzystuje IoC pod maską. Fasada wie wszystko o odbiorcach, ponieważ musi przekierowywać do nich połączenia. Magistrala ma zerowe sprzężenie; fasada ma nieprzyzwoicie wysokie połączenie wylotowe. Prawie wszystko, co zmienisz, w dowolnym miejscu, wpłynie również na fasadę.
Aaronaught
Myślę, że część zamieszania tutaj - i widzę to dość często - jest właśnie tym, co oznacza zależność . Jeśli masz klasę, która zależy od jednej innej klasy, ale wywołuje 4 metody tej klasy, ma 4 zależności, a nie 1. Umieszczenie tego wszystkiego za fasadą nie zmienia liczby zależności, tylko utrudnia ich zrozumienie .
Aaronaught
0

Dlaczego nie połączyć obu?

Stwórz fasadę i umieść wszystkie usługi, z których korzystają twoje modele. Wtedy możesz mieć jedną fasadę dla wszystkich swoich modeli bez złego słowa S.

Lub możesz użyć zastrzyku właściwości zamiast zastrzyku konstruktora. Ale musisz upewnić się, że są one odpowiednio wstrzykiwane.

Euforyk
źródło
To byłaby lepsza odpowiedź, jeśli podałeś przykład w pseudo-C #.
Robert Harvey,