Czy ViewModel powinien być głupi?

0

Hej, jeśli chodzi o kontroler z wzorca MVC, no to powinien być on jak najgłupszy i wszystkie zadania delegować do serwisów. A co z ViewModelem z MVVM? Czy tutaj sytuacja jest analogiczna? Czy może wręcz przeciwnie i ViewModel może robić sporo więcej?

1

W MVVM Viewmodel to taki "wirtualny' widok więc sam widok powinien być jeszcze głupszy.
Teoretycznie ViewModel powinien wypluwać coś z czym bindujesz tylko faktyczne kontrolki/komponenty - taki trochę plik .dfm z Delphi albo projekty formatek z Cpp.
Oczywiście wszystko zależy do jakiego poziomu abstrakcji wyciągasz to pojęcie.

0

A co z komendami? Przecież one są częścią właśnie ViewModelu.

2
Juhas napisał(a):

Hej, jeśli chodzi o kontroler z wzorca MVC, no to powinien być on jak najgłupszy i wszystkie zadania delegować do serwisów.

Kontroler raczej powinien delegować zadania do modelu, w takim klasycznym MVC.

3
Juhas napisał(a):

Hej, jeśli chodzi o kontroler z wzorca MVC, no to powinien być on jak najgłupszy i wszystkie zadania delegować do serwisów. A co z ViewModelem z MVVM? Czy tutaj sytuacja jest analogiczna? Czy może wręcz przeciwnie i ViewModel może robić sporo więcej?

ViewModel jest mniej więcej odpowiednikiem kontrolera, i odpowiada za logikę prezentacji. Od kontrolera różni się generalnie obsługą eventów i trzymaniem stanu.
https://docs.microsoft.com/en-us/previous-versions/msp-n-p/gg405484(v=pandp.40)

0

@somekind: Czyli obsługa wszystkich komend powinna odbywać się w innym miejscu?
Dokładnie o to chcę zapytać. Czy komenda może być wykonywana w ViewModelu, czy powinna iść przez inną warstwę, np. zapis dokumentu. Załóżmy, że ta metoda jest uruchamiana przez komendę:

bool CanSaveDocument()
{
    return ActiveDocument != null;
}

async Task SaveDocument()
{
    if(ActiveDocument == null)
        return;

    UIAction(() => ShowProgress());
    await db.Save(ActiveDocument);   
    UIAction(() => HideProgress());
}

ShowProgress/HideProgress to po prostu metody, które w jakiś sposób mówią widokowi, że ma pokazać progress.

Dla ułatwienia dałem uproszczony kod, ale widać, że ViewModel ma zależność od jakiegoś repozytorium i może tutaj wykonywać inne operacje. Czy może lepszym jest:

async Task SaveDocument()
{
    UIAction(() => ShowProgress());
    await documentService.Save(ActiveDocument);
    UIAction(() => HideProgress());
}

Tutaj ViewModel nie ma nic wspólnego z warstwą bazodanową. Odwołuje się tylko do jakiegoś serwisu (ewentualnie polecenia - w sensie wzorca behawioralnego).

A jeśli mamy zapisać wszystkie otwarte dokumenty? To też pytanie jak powinno to być wg sztuki. Tak:

async Task SaveAllDocuments()
{
    UIAction(() => ShowProgress());
    await documentService.SaveAll(OpenedDocuments);
    UIAction(() => HideProgress());
}

czy tak:

async Task SaveDocument()
{
    UIAction(() => ShowProgress());
    
    foreach(var doc in OpenedDocuments)
        await documentService.Save(doc); //czy może też zależne od repozytorium

    UIAction(() => HideProgress());
}
4

Z MVVM nie pracowałem za wiele, ale im mniej zależności i im głupszy tym lepiej - łatwiej też testować. Testowalność ma kluczowe znaczenie przy wyborze struktury kodu. A przynajmniej powinna :-).
Zapis do bazy to nie logika prezentacji i nie należy do ViewModelu. W Twoim przykładzie serwis to jest taka przelotka do która woła repozytorium. Przy takim przykładzie nie widać sensu stosowania osobnej warstwy. Jeśli jednak zamiast wołania samego "await db.Save(ActiveDocument);" umieścisz tam kilka(naście) linii kodu zaczyna to nabierać sensu i wyraźnie widać że repozytorium powinno NIE być tutaj dostępne, a być częścią czegoś "większego" np SaveService (robienie klas typu DocumentService gdzie jest 5 metod często "brzydko pachnie").

Dodam, że moim zdaniem samo " if(ActiveDocument == null) return;" może tam sobie zostać jako część logiki prezentacji, zwłąszcza że masz CanSave metodę która np może decydować czy w ogóle przycisk jest aktywny/widoczny.
Co do pytania drugiego SaveAll vs Foreach - ja bym powiedział zrób tak, żeby było wygodnie i sensownie. To też może być wygodniej umieścić w osobnej warstwie jeśli np chcesz tam jakoś obsłużyć scenariusz, gdy tylko część z dokumentów się uda zapisać. I co wtedy? No to łatwiej to testować też poza ViewModelem.

0

@Juhas: no jak dla mnie Twoje oba kody się specjalnie nie różnią. W obu ViewModel woła Model do wykonania jakiejś tam operacji. A jak wygląda architektura modelu, to jest inna sprawa. Mogą być serwisy, mogą być handlery poleceń.

0

Jeszcze jedno pytanie. Co sądzicie o umieszczaniu ViewModelu wewnątrz innego ViewModelu?
Jest MainViewModel, który posiada DocumentListViewModel. MainViewModel to po prostu ViewModel dla głównego okna aplikacji, w którym mamy podstawowe operacje, jak również widzimy listę dokumentów. Możemy utworzyć nowy dokument itd.

Moim zdaniem to złe podejście i ViewModel nie powinien używać innych ViewModeli w sobie. Z drugiej strony może to być "wygodne". Jak uważacie?

1

OK, z tego co czytam, to wszędzie właściwie piszą, że to normalne, że w jednym ViewModelu znajduje się inny.

1

VM nie jest głupi, to on skleja interaktory i daje komendy do V, odbiera akcje z V.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.