Czy w MVVM ViewModel lub Model powinien implementować INotifyPropertyChanged?

165

Większość przykładów MVVM, przez które pracowałem, miało implementację ModelINotifyPropertyChanged , ale w przykładzie CommandSink Josha Smitha implementuje ViewModelINotifyPropertyChanged .

Nadal poznawczo łączę koncepcje MVVM, więc nie wiem, czy:

  • INotifyPropertyChangedAby zabrać się CommandSinkdo pracy, musisz wstawić ViewModel
  • To tylko aberracja normy i nie ma to większego znaczenia
  • Zawsze powinieneś mieć implementację Model INotifyPropertyChangedi jest to tylko błąd, który zostałby poprawiony, gdyby został opracowany z przykładu kodu do aplikacji

Jakie były doświadczenia innych z projektami MVVM, nad którymi pracowałeś?

Edward Tanguay
źródło
4
jeśli wdrożysz INPC, wypróbuj github.com/Fody/PropertyChanged - pozwoli to zaoszczędzić tygodnie pisania.
Facet z CAD

Odpowiedzi:

104

Powiedziałbym wręcz przeciwnie, zawsze umieszczam mój INotifyPropertyChangedna moim ViewModel - naprawdę nie chcesz zanieczyszczać swojego modelu funkcją dość specyficzną dla WPF, na przykład INotifyPropertyChangedte rzeczy powinny znajdować się w ViewModel.

Jestem pewien, że inni by się nie zgodzili, ale tak właśnie pracuję.

Steven Robbins
źródło
84
Co robisz, gdy właściwość zmienia się w modelu? Musisz jakoś przenieść to do modelu widoku. Szczerze mówiąc, teraz mam do czynienia z tą zagadką.
Roger Lipscombe
4
EventAggregator w kodzie Prism jest dobrą alternatywą dla INotifyPropertyChanged w modelu, ze zmienionym typem zdarzenia niestandardowej właściwości. Kod zdarzenia w tym projekcie obsługuje przekazywanie zdarzeń między wątkami w tle i UI, co czasami może stanowić problem.
Steve Mitcham
51
INotifyProperyChanged nie jest specyficzne dla WPF, żyje w przestrzeni nazw System.ComponentModel, używałem go w aplikacjach WinForms, również INotifyPropertyChanged jest w .Net od 2.0, WPF istnieje dopiero od 3.0
benPearce
40
Jestem fanem umieszczania INotifyPropertyChanged zarówno w MODELU, jak i VIEWMODEL. Nie przychodzi mi do głowy żaden powód, żeby tego nie robić. Jest to elegancki sposób informowania VIEWMODEL o tym, kiedy w Twoim TRYBIE zaszły zmiany tła, które wpływają na VIEWMODEL, tak jak jest on używany do informowania VIEW i nastąpiły zmiany w VIEWMODEL.
ScottCher
6
@Steve - o informowaniu ViewModel o zmianie właściwości Modelu, wygląda na to, że INotifyPropertyChanged działa dobrze jako „zdarzenie, do którego Viewmodel może się podłączyć”. Dlaczego tego nie używać?
skybluecodeflier
146

Zdecydowanie nie zgadzam się z koncepcją, że Model nie powinien implementować INotifyPropertyChanged. Ten interfejs nie jest specyficzny dla interfejsu użytkownika! Po prostu informuje o zmianie. Rzeczywiście, WPF intensywnie wykorzystuje to do identyfikowania zmian, ale to nie znaczy, że jest to interfejs interfejsu użytkownika. Porównałbym to do komentarza: „ Opona to akcesorium samochodowe ”. Jasne, ale korzystają z niego także rowery, autobusy itp. Podsumowując, nie traktuj tego interfejsu jako elementu interfejsu użytkownika.

Powiedziawszy to, niekoniecznie oznacza to, że uważam, że Model powinien dostarczać powiadomienia. W rzeczywistości model nie powinien implementować tego interfejsu, chyba że jest to konieczne. W większości przypadków, gdy żadne dane serwera nie są przekazywane do aplikacji klienckiej, model może być nieaktualny. Ale słuchając danych z rynków finansowych, to nie widzę, dlaczego model nie może zaimplementować interfejsu. Na przykład, co zrobić, jeśli mam logikę inną niż UI, taką jak usługa, która po otrzymaniu ceny kupna lub sprzedaży dla danej wartości generuje alert (np. Za pośrednictwem wiadomości e-mail) lub składa zamówienie? Może to być możliwe czyste rozwiązanie.

Istnieją jednak różne sposoby osiągnięcia pewnych rzeczy, ale zawsze opowiadałbym się za prostotą i unikaniem nadmiarowości.

Co jest lepsze? Definiowanie zdarzeń w kolekcji lub zmian właściwości w modelu widoku i propagowanie ich do modelu lub sprawienie, aby widok wewnętrznie aktualizował model (za pośrednictwem modelu widoku)?

Podsumowując, ilekroć zobaczysz kogoś, kto twierdzi, że „ nie możesz zrobić tego lub tamtego ”, jest to znak, że nie wie, o czym mówi.

To naprawdę zależy od twojego przypadku i tak naprawdę MVVM to framework z wieloma problemami i nie widzę jeszcze wspólnej implementacji MVVM we wszystkich dziedzinach.

Chciałbym mieć więcej czasu na wyjaśnienie wielu odmian MVVM i kilku rozwiązań typowych problemów - w większości dostarczonych przez innych programistów, ale myślę, że będę musiał to zrobić innym razem.

Paulo Sousa
źródło
7
Pomyśl o tym w ten sposób. Jeśli jako programista korzystasz z pliku .dll z modelami w tobie, z pewnością nie napisałbyś ich ponownie, aby obsługiwać INotifyPropertyChanged.
Lee Treveil,
2
Całkowicie się z tobą zgadzam. Podobnie jak ja, możesz również być zadowolony, gdy dowiesz się, że oficjalna dokumentacja MVVM < msdn.microsoft.com/en-us/library/gg405484%28v=pandp.40%29.aspx > (sekcja Model) zgadza się z nami. :-)
Noldorin
„Istnieją jednak różne sposoby osiągnięcia pewnych rzeczy, ale zawsze opowiadałbym się za prostotą i unikaniem nadmiarowości”. Bardzo ważne.
Bastien Vandamme
1
INotifyPropertyChangedjest częścią System.ComponentModelprzestrzeni nazw, która służy do „ zachowania komponentów i formantów w czasie wykonywania i projektowania w czasie projektowania ”. NIE UŻYWAJ INotifyPropertyChanged w modelach, tylko w ViewModels. Łącze do dokumentów: docs.microsoft.com/en-us/dotnet/api/system.componentmodel
Igor Popov
1
Wiem, stary post, ale często do niego wracam rozpoczynając nowy projekt z MVVM. Niedawno zacząłem bardziej rygorystycznie egzekwować zasadę pojedynczej odpowiedzialności. Model ma mieć jedną odpowiedzialność. Być modelką. Po dodaniu INotifyPropertyChanged do modelu nie jest on już zgodny z zasadą pojedynczej odpowiedzialności. Cały powód istnienia ViewModel polega na tym, aby model był modelem, aby model miał jedną odpowiedzialność.
Rhyous,
13

Myślę, że MVVM jest bardzo słabo nazwane i wywołanie ViewModel a ViewModel powoduje, że wielu pomija ważną cechę dobrze zaprojektowanej architektury, którą jest DataController, który kontroluje dane bez względu na to, kto próbuje ich dotknąć.

Jeśli myślisz o modelu widoku jako bardziej o kontrolerze danych i zaimplementujesz architekturę, w której kontroler danych jest jedynym elementem, który dotyka danych, nigdy nie dotykasz danych bezpośrednio, ale zawsze używasz DataController. DataController jest przydatny dla interfejsu użytkownika, ale niekoniecznie tylko dla interfejsu użytkownika. To jest dla warstwy biznesowej, warstwy interfejsu użytkownika itp ...

DataModel -------- DataController ------ View
                  /
Business --------/

Skończysz z takim modelem. Nawet firma powinna dotykać danych tylko za pomocą ViewModel. Wtedy twoja zagadka po prostu znika.

Rhyous
źródło
3
To świetnie, jeśli dane zmieniają się tylko wtedy, gdy DataController je zmienia. Jeśli dane pochodzą z bazy danych lub innego magazynu danych, który może zapewnić inną drogę do zmiany, może być potrzebny sposób poinformowania VIEWMODEL (DataController we wzorcu) i VIEW, kiedy to się stanie. Możesz sondować przy użyciu DataController lub wypychać z jakiegoś zewnętrznego procesu do Twojego DataModel i zezwolić Twojemu DataModel na wysyłanie powiadomień o zmianach do Twojego DataController.
ScottCher
4
Masz rację. Wzory projektowe są na bardzo wysokim poziomie. W większości przypadków wzorzec projektowy prowadzi cię do robienia rzeczy dobrze, ale od czasu do czasu kierują cię w niewłaściwy sposób. Nigdy nie powinieneś unikać robienia czegoś dobrze, ponieważ jest to poza twoim wzorcem projektowym.
Rhyous
Możesz również wypchnąć do kontrolera DataController, ponieważ kontroluje i model danych, i powiesz mu, aby zaktualizował.
Rhyous,
Ponadto model w MVVM powinien być dostosowany do potrzeb interfejsu użytkownika (np. DTO). Zatem każda baza danych lub złożona logika biznesowa powinna mieć miejsce w innej warstwie, a następnie surowe dane powinny być dostarczane za pośrednictwem modelu widoku.
Imię Jack,
9

To zależy od tego, jak zaimplementowałeś swój model. Moja firma używa obiektów biznesowych podobnych do obiektów CSLA firmy Lhotka i szeroko korzysta z nichINotifyPropertyChanged całym modelu biznesowym.

Nasz silnik walidacji w dużej mierze polega na otrzymywaniu powiadomienia, że ​​właściwości zmieniają się za pośrednictwem tego mechanizmu i działa on bardzo dobrze. Oczywiście, jeśli używasz innej implementacji niż obiekty biznesowe, gdzie powiadamianie o zmianach nie jest tak krytyczne dla operacji, możesz mieć inne metody wykrywania zmian w modelu biznesowym.

Mamy również modele widoku, które w razie potrzeby propagują zmiany z modelu, ale same modele widoku nasłuchują podstawowych zmian modelu.

Steve Mitcham
źródło
3
Jak dokładnie propagujesz OnPropertyChanged Model do OnPropertyChanged ViewModel? Mam problem, gdy ViewModel ma inne nazwy właściwości niż Model - wtedy potrzebne byłoby jakieś mapowanie nazwa-nazwa, prawda?
Martin Konicek
To nie jest nic naprawdę wyrafinowanego, po prostu przekazujemy wydarzenia. Przypuszczam, że gdyby nazwy były różne, można by użyć tabeli przeglądowej. Jeśli zmiana nie była mapowaniem jeden do jednego, możesz po prostu podłączyć zdarzenie, a następnie uruchomić niezbędne zdarzenia w module obsługi.
Steve Mitcham
6

Zgadzam się z odpowiedzią Paulo, implementacja INotifyPropertyChangedw modelach jest całkowicie akceptowalna, a nawet sugerowana przez Microsoft -

Zazwyczaj model implementuje udogodnienia, które ułatwiają powiązanie z widokiem. Zwykle oznacza to, że obsługuje powiadomienia o zmianie właściwości i kolekcji za pośrednictwem interfejsów INotifyPropertyChangedi INotifyCollectionChanged. Klasy modeli, które reprezentują kolekcje obiektów, zwykle pochodzą z ObservableCollection<T>klasy, która zapewnia implementację INotifyCollectionChangedinterfejsu.

Chociaż to od Ciebie zależy, czy chcesz tego typu implementacji, czy nie, ale pamiętaj -

Co się stanie, jeśli klasy modeli nie implementują wymaganych interfejsów?

Czasami trzeba będzie pracować z modelu obiektów, które nie wdrażają tych INotifyPropertyChanged, INotifyCollectionChanged, IDataErrorInfolub INotifyDataErrorInfointerfejsów. W takich przypadkach model widoku może wymagać zawijania obiektów modelu i uwidocznienia wymaganych właściwości w widoku. Wartości tych właściwości zostaną dostarczone bezpośrednio przez obiekty modelu. Model widoku zaimplementuje wymagane interfejsy dla właściwości, które uwidacznia, tak aby widok mógł łatwo powiązać z nimi dane.

Pochodzą z - http://msdn.microsoft.com/en-us/library/gg405484(PandP.40).aspx

Pracowałem przy kilku projektach, w których nie wdrożyliśmy ich INotifyPropertyChangedw naszych modelach i przez to napotkaliśmy wiele problemów; konieczne było niepotrzebne powielanie właściwości w VM i jednocześnie musieliśmy aktualizować bazowy obiekt (zaktualizowanymi wartościami) przed przekazaniem ich do BL / DL.

Napotkasz problemy szczególnie, jeśli będziesz musiał pracować z kolekcją obiektów swojego modelu (powiedzmy na edytowalnej siatce lub liście) lub złożonymi modelami; obiekty modelu nie będą aktualizowane automatycznie i będziesz musiał zarządzać tym wszystkim na swojej maszynie wirtualnej.

akjoshi
źródło
3

Ale czasami (jak w tym tekście linku do prezentacji ) model to usługa, która dostarcza aplikacji część danych w trybie online, a następnie trzeba wprowadzić powiadomienie o pojawieniu się nowych danych lub zmianie danych za pomocą zdarzeń ...

Andrey Khataev
źródło
3

Myślę, że odpowiedź jest dość jasna, jeśli chcesz stosować się do MV-VM.

patrz: http://msdn.microsoft.com/en-us/library/gg405484(v=PandP.40).aspx

We wzorcu MVVM widok hermetyzuje interfejs użytkownika i dowolną logikę interfejsu użytkownika, model widoku hermetyzuje logikę i stan prezentacji, a model hermetyzuje logikę biznesową i dane.

„Widok współdziała z modelem widoku poprzez powiązanie danych, polecenia i zdarzenia powiadamiania o zmianach. Model widoku wysyła zapytania, obserwuje i koordynuje aktualizacje modelu, konwertując, weryfikując i agregując dane niezbędne do wyświetlenia w widoku”.

John D.
źródło
4
Cytat podlega interpretacji. Myślę, że powinieneś dodać swoją interpretację, aby twoja odpowiedź była jasna :-)
Søren Boisen
@John D: Ten artykuł podaje tylko jedną interpretację MVVM i sposób jej implementacji, nie definiuje MVVM.
akjoshi
Co więcej, jeśli przeczytasz cały artykuł, definiuje on klasę Model w następujący sposób: „Zazwyczaj model implementuje udogodnienia, które ułatwiają powiązanie z widokiem. Zwykle oznacza to, że obsługuje powiadomienia o zmianie właściwości i kolekcji za pośrednictwem interfejsów INotifyPropertyChanged i INotifyCollectionChanged . Klasy modeli, które reprezentują kolekcje obiektów, zwykle pochodzą z klasy ObservableCollection <T>, która zapewnia implementację interfejsu INotifyCollectionChanged. "
akjoshi
2

Powiedziałbym w twoim ViewModel. Nie jest częścią modelu, ponieważ jest on niezależny od interfejsu użytkownika. Model powinien być „wszystkim poza biznesem agnostykiem”

Steve Dunn
źródło
2

Implementacja INPC w modelach może być stosowana, jeśli modele są wyraźnie widoczne w ViewModel. Ale ogólnie rzecz biorąc, ViewModel zawija modele to jego własne klasy, aby zmniejszyć złożoność modelu (co nie powinno być przydatne w przypadku wiązania). W takim przypadku INPC należy zaimplementować w ViewModel.

stéphane Boutinet
źródło
1

Używam INotifyPropertyChangeinterfejsu w modelu. W rzeczywistości zmiana właściwości modelu powinna być uruchamiana tylko przez interfejs użytkownika lub klienta zewnętrznego.

Zauważyłem kilka zalet i wad:

Zalety

Zgłaszający działa w modelu biznesowym

  1. Jeśli chodzi o domenę, jest to właściwe. Powinien zdecydować, kiedy podbijać, a kiedy nie.

Niedogodności

Model ma właściwości (ilość, stawka, prowizja, suma kosztów). Całkowita cena jest obliczana na podstawie ilości, stawki, zmiany prowizji.

  1. Przy ładowaniu wartości z db, obliczenie całkowitego frieght jest wywoływane 3 razy (ilość, stawka, prowizja). Powinien być raz.

  2. Jeśli stopa, ilość jest przypisana w warstwie biznesowej, ponownie wywoływany jest notifier.

  3. Powinna istnieć opcja wyłączenia tego, prawdopodobnie w klasie bazowej. Jednak programiści mogli o tym zapomnieć.

Anand Kumar
źródło
Ze względu na wszystkie wady, które wymieniliście, właśnie zdecydowaliśmy, że w naszym stosunkowo dużym projekcie WPF zaimplementowanie INPC w modelach była ZŁĄ decyzją. Powinny zajmować się tylko warstwą trwałości. Wszystkie inne rzeczy, takie jak walidacja, powiadomienie o zmianie i obliczone właściwości, powinny być obsługiwane w ViewModel. Teraz wyraźnie rozumiem, że powtarzanie właściwości modelu w ViewModel NIE zawsze jest naruszeniem zasady DRY.
try2fly.b4ucry
1

Myślę, że wszystko zależy od przypadku użycia.

Kiedy masz prosty model z mnóstwem właściwości, możesz go zaimplementować w INPC. Mówiąc prościej, mam na myśli, że ten model wygląda raczej jak POCO .

Jeśli Twój model jest bardziej złożony i żyje w domenie modelu interaktywnego - modele odwołujące się do modeli, subskrybujące zdarzenia innych modeli - posiadanie zdarzeń modelowych zaimplementowanych jako INPC jest koszmarem.

Postaw się w pozycji jakiejś modelowej istoty, która musi współpracować z innymi modelami. Masz różne wydarzenia do zasubskrybowania. Wszystkie z nich są zaimplementowane jako INPC. Wyobraź sobie te programy obsługi zdarzeń, które masz. Jedna ogromna kaskada klauzul if i / lub klauzul przełączających.

Kolejny problem z INPC. Powinieneś projektować swoje aplikacje tak, aby opierały się na abstrakcji, a nie implementacji. Odbywa się to zwykle za pomocą interfejsów.

Rzućmy okiem na 2 różne implementacje tej samej abstrakcji:

public class ConnectionStateChangedEventArgs : EventArgs
{
    public bool IsConnected {get;set;}
}

interface IConnectionManagerINPC : INotifyPropertyChanged
{
    string Name {get;}
    int ConnectionsLimit {get;}
    /*

    A few more properties

    */
    bool IsConnected {get;}
}

interface IConnectionManager
{
    string Name {get;}
    int ConnectionsLimit {get;}
    /*

    A few more properties

    */
    event EventHandler<ConnectionStateChangedEventArgs> ConnectionStateChanged;
    bool IsConnected {get;}
}

Teraz spójrz na nich obu. Co mówi IConnectionManagerINPC? Że niektóre jego właściwości mogą się zmienić. Nie wiesz, który z nich. W rzeczywistości projekt jest taki, że zmienia się tylko IsConnected, ponieważ reszta z nich jest tylko do odczytu.

Z drugiej strony, intencje IConnectionManager są jasne: „Mogę powiedzieć, że wartość mojej właściwości IsConnected może się zmienić”.

dzendras
źródło
1

Po prostu użyj INotifyPropertyChangew swoim modelu widoku, a nie w modelu,

model zwykle używa IDataErrorInfodo obsługi błędów walidacji, więc po prostu pozostań w swoim ViewModel i jesteś na dobrej drodze do MVVM.

Adam
źródło
1
A co z modelami o kilku właściwościach? powtarzasz kod w maszynie wirtualnej.
Luis
0

Załóżmy, że odniesienie do obiektu w Twoim widoku ulegnie zmianie. W jaki sposób powiadomisz wszystkie właściwości o konieczności aktualizacji, aby wyświetlały prawidłowe wartości? Z OnPropertyChangedmojego punktu widzenia odwoływanie się do wszystkich właściwości obiektu jest bzdurą.

Więc co mogę zrobić, to niech sam obiekt do powiadamiania kogokolwiek, gdy wartość nieruchomości ulegnie zmianie, a moim zdaniem jak używam powiązań Object.Property1, Object.Property2i tak dalej. W ten sposób, jeśli chcę tylko zmienić obiekt, który jest aktualnie obsługiwany w moim widoku, po prostu robię OnPropertyChanged("Object").

Aby uniknąć setek powiadomień podczas ładowania obiektów, mam prywatny wskaźnik boolowski, który ustawiam na true podczas ładowania, który jest sprawdzany z obiektu OnPropertyChangedi nic nie robi.

Dummy01
źródło
0

Zwykle ViewModel zaimplementuje INotifyPropertyChanged. Model może być cokolwiek (plik xml, baza danych lub nawet obiekt). Model służy do przekazywania danych do modelu widoku, który jest propagowany do widoku.

Spójrz tutaj

Syed
źródło
1
emm .. nie. Model nie może być plikiem XML ani bazą danych. Model nie jest używany do podania danych. W przeciwnym razie nie powinien nazywać się „model”, ale „dane” ..? Model służy do opisu danych. Całkiem nie wymaga wyjaśnienia, prawda? :)
Taras
1
Jeśli masz lepszą odpowiedź, udostępnij! wszyscy jesteśmy tutaj, aby dzielić się wiedzą i nie konkurować
Adam
0

imho myślę, że Viewmodel implementuje INotifyPropertyChangei model mógłby używać powiadomienia na innym „poziomie”.

np. w przypadku niektórych usług dokumentacyjnych i obiektu dokumentu masz zdarzenie documentChanged, którego słucha viewmodel, aby wyczyścić i odbudować widok. W modelu widoku edycji masz odpowiednią zmianę właściwości dokumentu w celu obsługi widoków. Jeśli usługa dużo robi z dokumentem przy zapisywaniu (aktualizacja daty zmiany, ostatniego użytkownika itd.), Łatwo dostajesz przeładowanie Ipropertozmienionych zdarzeń i wystarczy zmienić dokument.

Ale jeśli używasz INotifyPropertyChangew swoim modelu, myślę, że dobrą praktyką jest przekazywanie go w swoim modelu widoku zamiast subskrybowania go bezpośrednio w swoim widoku. W takim przypadku, gdy zdarzenia zmieniają się w modelu, wystarczy zmienić model widoku, a widok pozostaje nietknięty.

Bram
źródło
0

Wszystkie właściwości, które są powiązane z moim widokiem, znajdują się w moich ViewModel (ach). Dlatego powinny zaimplementować interfejs INotifyPropertyChanged. Dlatego Widok otrzymuje wszystkie zmiany.

[Korzystając z zestawu narzędzi MVVM Light, pozwoliłem im dziedziczyć z ViewModelBase.]

Model zawiera logikę biznesową, ale nie ma nic wspólnego z widokiem. Dlatego nie ma potrzeby stosowania interfejsu INotifyPropertyChanged.

donotbefake
źródło