Przepraszam za to długie pytanie, brzmi to trochę jak rant, ale obiecuję, że nie! Podsumowałem moje pytania poniżej
W świecie MVC rzeczy są proste. Model ma stan, widok pokazuje model, a kontroler robi rzeczy do / z modelem (w zasadzie), kontroler nie ma stanu. Aby to zrobić , kontroler ma pewne zależności od usług sieciowych, repozytorium, partii. Kiedy tworzysz instancję kontrolera, zależy ci na dostarczeniu tych zależności, nic więcej. Wykonując akcję (metodę na kontrolerze), używasz tych zależności, aby pobrać lub zaktualizować model lub wywołać inną usługę domeny. Jeśli istnieje jakikolwiek kontekst, powiedzmy, że użytkownik chce zobaczyć szczegóły konkretnego elementu, przekazujesz identyfikator tego elementu jako parametr do działania. Nigdzie w kontrolerze nie ma odniesienia do żadnego stanu. Jak na razie dobrze.
Wpisz MVVM. Uwielbiam WPF, uwielbiam wiązanie danych. Uwielbiam frameworki, dzięki którym wiązanie danych z ViewModels jest jeszcze łatwiejsze (przy użyciu bankomatu Caliburn Micro). Wydaje mi się jednak, że na tym świecie rzeczy są mniej proste. Zróbmy ćwiczenie ponownie: Model posiada stan, widok pokazy ViewModel i ViewModel robi rzeczy do / z modelu (w zasadzie), ViewModel ma mieć stan! (wyjaśnienie; może to delegaci wszystkich właściwości do jednego lub więcej modeli, ale to oznacza, musi mieć odniesienie do modelu taki czy inny sposób, który jest stan w sobie) Aby zrobićrzeczy ViewModel ma pewne zależności od usług sieciowych, repozytorium, partii. Kiedy tworzysz ViewModel, zależy ci na dostarczeniu tych zależności, ale także na stanie. I to, panie i panowie, denerwuje mnie bez końca.
Ilekroć musisz utworzyć instancję ProductDetailsViewModel
z ProductSearchViewModel
(z którego zadzwoniłeś, ProductSearchWebService
który z kolei wrócił IEnumerable<ProductDTO>
, wszyscy nadal są ze mną?), Możesz wykonać jedną z następujących czynności:
- zadzwoń
new ProductDetailsViewModel(productDTO, _shoppingCartWebService /* dependcy */);
, to źle, wyobraź sobie 3 dodatkowe zależności, oznacza to, żeProductSearchViewModel
musisz również wziąć na siebie te zależności. Również zmiana konstruktora jest bolesna. - zadzwoń
_myInjectedProductDetailsViewModelFactory.Create().Initialize(productDTO);
, fabryka to tylko Func, są one łatwo generowane przez większość platform IoC. Myślę, że to źle, ponieważ metody Init są nieszczelną abstrakcją. Nie można również użyć słowa kluczowego tylko do odczytu dla pól ustawionych w metodzie Init. Jestem pewien, że jest kilka innych powodów. - call
_myInjectedProductDetailsViewModelAbstractFactory.Create(productDTO);
Więc ... jest to wzorzec (fabryka abstrakcyjna), który jest zwykle zalecany dla tego typu problemów. Myślałem, że to genialne, ponieważ zaspokaja moje pragnienie pisania statycznego, dopóki nie zacząłem go używać. Ilość kodu na schemacie to chyba za dużo (no wiesz, poza śmiesznymi nazwami zmiennych, z których korzystam). Dla każdego ViewModel, który potrzebuje parametrów środowiska wykonawczego, otrzymasz dwa dodatkowe pliki (interfejs fabryczny i implementacja), i musisz wpisać zależności inne niż środowisko wykonawcze, takie jak 4 dodatkowe czasy. I za każdym razem, gdy zmieniają się zależności, możesz to zmienić również w fabryce. Wydaje mi się, że nawet nie używam już pojemnika DI. (Myślę, że Castle Windsor ma na to jakieś rozwiązanie [z własnymi wadami, poprawcie mnie, jeśli się mylę]). - zrób coś z anonimowymi typami lub słownikiem. Lubię moje pisanie statyczne.
Więc tak. Mieszanie stanu i zachowania w ten sposób stwarza problem, który w ogóle nie istnieje w MVC. I wydaje mi się, że obecnie nie ma naprawdę odpowiedniego rozwiązania tego problemu. Teraz chciałbym obserwować kilka rzeczy:
- Ludzie faktycznie używają MVVM. Więc albo nie przejmują się wszystkimi powyższymi, albo mają jakieś genialne inne rozwiązanie.
- Nie znalazłem szczegółowego przykładu MVVM z WPF. Na przykład projekt próbki NDDD ogromnie pomógł mi zrozumieć niektóre koncepcje DDD. Naprawdę chciałbym, gdyby ktoś mógł skierować mnie w stronę czegoś podobnego do MVVM / WPF.
- Może źle robię MVVM i powinienem odwrócić swój projekt do góry nogami. Może wcale nie powinienem mieć tego problemu. Wiem, że inni ludzie zadawali to samo pytanie, więc myślę, że nie jestem jedyny.
Podsumowując
- Czy mam rację, stwierdzając, że fakt, że ViewModel jest punktem integracji zarówno stanu, jak i zachowania, jest przyczyną pewnych trudności z wzorzec MVVM jako całości?
- Czy używanie abstrakcyjnego wzorca fabrycznego jest jedynym / najlepszym sposobem na utworzenie modelu ViewModel w sposób statyczny?
- Czy dostępna jest coś w rodzaju szczegółowej implementacji referencyjnej?
- Czy posiadanie dużej liczby modeli ViewModels, których stan / zachowanie ma zapach projektowy?
Odpowiedzi:
Problem zależności podczas inicjowania nowego modelu widoku można rozwiązać za pomocą IOC.
Podczas konfigurowania pojemnika ...
Gdy potrzebujesz modelu widoku:
Podczas korzystania ze szkieletu, takiego jak kaliburn micro, często istnieje już pewna forma pojemnika IOC.
źródło
Pracuję codziennie z ASP.NET MVC i pracuję nad WPF od ponad roku i tak to widzę:
MVC
Kontroler powinien koordynować działania (pobierz to, dodaj to).
Widok jest odpowiedzialny za wyświetlanie modelu.
Model zazwyczaj obejmuje dane (np. UserId, FirstName), a także stan (np. Tytuły) i zwykle jest specyficzny dla widoku.
MVVM
Model zazwyczaj przechowuje tylko dane (np. UserId, FirstName) i zwykle jest przekazywany
Model widoku obejmuje zachowanie widoku (metody), jego danych (model) i interakcji (poleceń) - podobnie jak aktywny wzorzec MVP, w którym prezenter zna model. Model widoku jest specyficzny dla widoku (1 widok = 1 model widoku).
Widok odpowiada za wyświetlanie danych i powiązanie danych z modelem widoku. Podczas tworzenia widoku zwykle jest z nim tworzony powiązany model widoku.
Należy pamiętać, że wzorzec prezentacji MVVM jest specyficzny dla WPF / Silverlight ze względu na ich charakter wiązania danych.
Widok zazwyczaj wie, z którym modelem widoku jest powiązany (lub jego abstrakcją).
Radziłbym, aby traktować model widoku jako singleton, nawet jeśli jest on tworzony na podstawie widoku. Innymi słowy, powinieneś być w stanie stworzyć go przez DI za pośrednictwem kontenera MKOl i wywołać odpowiednie metody, aby powiedzieć; wczytaj model na podstawie parametrów. Coś takiego:
Jako przykład w tym przypadku nie utworzyłbyś modelu widoku specyficznego dla aktualizowanego użytkownika - zamiast tego model zawierałby dane specyficzne dla użytkownika, które są ładowane przez jakieś wywołanie w modelu widoku.
źródło
Krótka odpowiedź na twoje pytania:
Długa wersja:
Stoimy w obliczu tego samego problemu i znaleźliśmy kilka rzeczy, które mogą ci pomóc. Chociaż nie znam „magicznego” rozwiązania, te rzeczy nieco łagodzą ból.
Wdrożenie modeli DTO do wiązania w celu śledzenia zmian i sprawdzania poprawności. Te „Dane” - modele podglądu nie mogą zależeć od usług i nie mogą pochodzić z kontenera. Mogą być po prostu „nowe”, przekazywane, a nawet wywodzić się z DTO. Najważniejsze jest wdrożenie modelu specyficznego dla twojej aplikacji (jak MVC).
Oddziel swoje ViewModels. Caliburn ułatwia łączenie ViewModels. Sugeruje to nawet za pomocą modelu ekranu / przewodnika. Ale to sprzężenie sprawia, że ViewModels jest trudny do testowania jednostkowego, stwarza wiele zależności i co najważniejsze: nakłada ciężar zarządzania cyklem życia ViewModel na twoje ViewModels. Jednym ze sposobów na ich rozdzielenie jest użycie usługi nawigacyjnej lub kontrolera ViewModel. Na przykład
interfejs publiczny IShowViewModels {void Show (object inlineArgumentsAsAnonymousType, string regionId); }
Jeszcze lepiej jest to robić za pomocą jakiejś formy wiadomości. Ale ważne jest, aby nie obsługiwać cyklu życia ViewModel z innych ViewModels. W kontrolerach MVC nie zależą od siebie, aw MVVM ViewModels nie powinny od siebie zależeć. Zintegruj je na kilka innych sposobów.
INeedData<T1,T2,...>
i wymuszenie parametrów tworzenia bezpiecznych dla typu, nie jest tego warte. Również tworzenie fabryk dla każdego typu ViewModel nie jest tego warte. Większość kontenerów IoC zapewnia rozwiązania tego problemu. Podczas działania wystąpią błędy, ale warto odłączyć i przetestować jednostkę. Nadal wykonujesz test integracji, a błędy te można łatwo wykryć.źródło
Sposób, w jaki zwykle to robię (używając PRISM), polega na tym, że każdy zestaw zawiera moduł inicjalizacji kontenera, w którym wszystkie interfejsy, instancje są rejestrowane podczas uruchamiania.
Biorąc pod uwagę twoje przykładowe klasy, można je zaimplementować w ten sposób, z pojemnikiem przechodzącym przez całą drogę. W ten sposób można łatwo dodawać nowe zależności, ponieważ masz już dostęp do kontenera.
Dość powszechna jest klasa ViewModelBase, z której pochodzą wszystkie modele widoków, która zawiera odwołanie do kontenera. Dopóki nauczysz się rozwiązywać wszystkie modele widoków zamiast
new()'ing
nich, powinno to znacznie uprościć rozwiązywanie zależności.źródło
Czasami dobrze jest przejść do najprostszej definicji, a nie pełnego przykładu: http://en.wikipedia.org/wiki/Model_View_ViewModel może być może czytanie przykładu Java ZK jest bardziej pouczające niż C #.
Innym razem słuchaj instynktu jelitowego ...
Czy twoje modele są odwzorowaniami obiektu na tabelę? Być może ORM pomógłby w mapowaniu do obiektów domeny podczas obsługi firmy lub aktualizacji wielu tabel.
źródło