Wzorzec architektoniczny Model View ViewModel (1/2) « Pabloware + Windows Phone

Pabloware + Windows Phone

Niezależny blog firmy Pabloware o systemie Windows® Phone 7

Wzorzec architektoniczny Model View ViewModel (1/2)

Zastosowanie na platformie Windows Phone 7 technologii Silverlight, pociąga za sobą popularność na niej wzorca architektonicznego MVVM. Mianowicie czytelnik dowie się, że wzorzec architektoniczny MVVM jest zdecydowanie wygodniejszy w stosowaniu, kiedy możliwe jest deklaratywne wiązanie danych (ang. binding) z kontrolkami interfejsu użytkownika (co nie znaczy, że nie można go stosować bez tego mechanizmu). Mechanizm takiego wiązania jest częścią bibliotek Silverlight oraz WPF, więc nic dziwnego, że z myślą o nich, ten wzorzec architektoniczny został opracowany i jest razem z nimi stosowany.

Można zaryzykować twierdzenie, że drugą przyczyną popularności wzorca architektonicznego MVVM na platformie Windows Phone 7 jest podział interfejsu użytkownika aplikacji na tej platformie na odrębne jednostki zwane stronami, co ułatwia zastosowanie tego wzorca, upraszczając identyfikację jego elementów.

Kod aplikacji o architekturze zgodnej ze wzorcem MVVM podzielony jest zgodnie z nazwą wzorca na trzy warstwy, które zostaną omówione poniżej oraz w następnym wpisie, uszeregowane według ich postępującej zależności.

Nakładanie się warstw wzorca architektonicznego MVVM

Warstwa Model

Zgodnie z nazwą, znajdują się tutaj klasy reprezentujące model biznesowy aplikacji, podobnie jak ma to miejsce, w tak samo nazwanej warstwie wzorca architektonicznego MVC (Model View Controller). Stosując zasadę pojedynczej odpowiedzialności (wzorzec projektowy SRP) i elementarne zasady projektowania obiektowego, dochodzimy do potwierdzenia, że w warstwie Model powinien znaleźć się wyłącznie kod modelu danych, na których działa aplikacja oraz kod zachowań tego modelu (logiki biznesowej).

Najbardziej oczywistą częścią klas należących do warstwy Model omawianego wzorca będą właściwości, które odpowiadają konkretnym danym, na których operuje aplikacja. Pojawia się tutaj ryzyko traktowania tych klas, jako prostych rekordów, służących do przenoszenia danych. Jak często w przypadku tego wzorca architektonicznego bywa, jest to kwestia nie do końca ustalona, ale można zasugerować czytelnikowi, traktowanie klas modelu, jako pełnowartościowych klas, a więc udostępniających, tam gdzie to możliwe, dane w postaci znaczącej dla reszty aplikacji, a nie jako dane surowe. Zgodnie, bowiem z dobrymi zasadami projektowania, gdybyśmy potraktowali klasy modelu, jako rekordy danych, nie moglibyśmy uzupełniać ich zachowaniami logiki biznesowej, bo doprowadziłoby to do mieszania obiektów z rekordami. Jeśli to konieczne, można w celu zachowania obiektowości modelu, umieścić dane w rekordach, które będą dołączone do obiektów modelu. Trzeba mieć jednak świadomość, że wprowadzi się tym kolejną warstwę do architektury aplikacji, nieprzewidzianą w powszechnie rozumianym wzorcu architektonicznym MVVM. Wydaje się, że jest to obszar wzorca do pilnego doprecyzowania, jeśli chcielibyśmy aby wzorzec był ścisły.

Innym, często spotykanym przykładem kodu, który umieszcza się w klasach warstwy Model, jest walidacja danych, bowiem można ją zaliczyć do logiki biznesowej. Autor umieszcza w tej warstwie również np. kod dokonujący obliczeń, co nie budzi wątpliwości, pod względem zgodności ze wzorcem, jeśli są to obliczenia należące do logiki biznesowej aplikacji. W klasach należących do warstwy Model można umieszczać również odnajdywanie referencji do powiązanych elementów modelu, zapewniając tym samym spójność odnajdywania modelu na poziomie samego modelu, przy czym właściwa praca odnajdywania może znaleźć się w klasie pomocniczej.

Podsumowując, można pokusić się o założenie, że w warstwie Model powinien znaleźć się taki kod logiki biznesowej aplikacji, który będzie miał jak najbardziej bezpośrednie zastosowanie w innych warstwach aplikacji, jako sedno jej funkcjonalności, ale nie, jako kod pomocniczy, np. związany z konkretnym środowiskiem uruchomieniowym aplikacji. W związku z tym kod modelu może być użyty bez żadnych zmian np. w wersjach biurkowej i webowej tej samej aplikacji. Wynika też z tego, że warstwa Model jest całkowicie niezależna od dwóch pozostałych warstw omawianego wzorca architektonicznego.

Warstwa ViewModel

Odpowiedzialnością klas znajdujących się w warstwie ViewModel jest dostarczenie z i do interfejsu użytkownika danych i zachowań, udostępnianych przez warstwę Model, w postaci, która przed zastosowaniem w interfejsie użytkownika, nie wymaga już żadnej modyfikacji. Oczywista jest związana z tym zależność pośredniczącej ze swej natury warstwy ViewModel od warstwy Model. Jednak zależność warstwy ViewModel od interfejsu użytkownika nie jest już tak oczywista. Spróbujmy poznać tę zależność.

Z pewnością warstwa ViewModel nie powinna bezpośrednio korzystać z interfejsu użytkownika, aby możliwa była jej kompilacja i testowanie bez posiadania tegoż (sposób odseparowania warstwy ViewModel od interfejsu użytkownika zostanie opisany przy okazji omawiania warstwy View). Jest bowiem, jedną z największych zalet stosowania wzorca architektonicznego MVVM, możliwość stosowania testów jednostkowych, na praktycznie całej programowej części aplikacji (warstwach Model i ViewModel). Gdyby warstwa ViewModel była bezpośrednio zależna od interfejsu użytkownika, stosowanie wobec niej testów jednostkowych byłoby z samej ich natury mocno utrudnione. Na takie trudności niestety natrafiamy nie stosując wzorca MVVM, bądź też innych metod izolacji interfejsu użytkownika od reszty aplikacji.

Z drugiej strony warstwa ViewModel jest funkcjonalnie całkowicie podporządkowana interfejsowi użytkownika. W związku z tym, przyjmuje się ogólną zasadę, że jednemu widokowi w interfejsie użytkownika (takiemu, jak np. okno z listą danych), powinna odpowiadać jedna klasa należąca do warstwy ViewModel.

Jednak najbardziej podstawowym przypadkiem klasy należącej do warstwy ViewModel, jest klasa opakowująca poszczególne właściwości odpowiadającej jej klasy, należącej do warstwy Model. Często właściwości te nie są przekazywane do interfejsu użytkownika w identycznej, jak obecna w warstwie Model postaci, co związane jest z koniecznością np. konwersji ich na inny typ danych lub też innej formy doprowadzenia ich do postaci, nadającej się do wyświetlenia w interfejsie użytkownika, np. poprzez zmianę ustrukturyzowania danych.

Dla porządku warto przypomnieć, że chodzi tu jedynie o przygotowanie danych do wyświetlenia, a nie np. o obliczenia na danych. Bowiem te, według przyjętego w tym wpisie rozumienia wzorca architektonicznego MVVM, winny odbyć się jeszcze w warstwie Model.

Innym mechanizmem, służącym do konwersji danych w celu ich wyświetlenia, są konwertery, wspomniane w poprzednim wpisie. Z powodów tam przedstawionych nie są one chętnie wykorzystywane przez autora, a ich zadanie jest oczywiście zlecane warstwie ViewModel. Co więcej wydaje się, że stosowanie konwerterów razem z warstwą ViewModel jest całkowicie zbędne, skoro duplikują one odpowiedzialność warstwy ViewModel. Ich użyciu może sprzyjać przesunięcie odpowiedzialności z warstwy Model na warstwę ViewModel lub bezpośrednie udostępnienie klas należących do warstwy Model w warstwie ViewModel, lecz oba nie są chyba zjawiskami właściwymi, z punktu widzenia zgodności ze wzorcem.

Z powyższego opisu dobrych klas należących do warstwy ViewModel, łatwo wyodrębnić dwa typy tych klas:

  • klasy odpowiadające podstawowej jednostce interfejsu użytkownika, np. stronie (ViewViewModel);
  • klasy odpowiadające typom warstwy Model (ModelViewModel).

Trzeba zaznaczyć, że nie jest to podział powszechnie przyjęty we wzorcu MVVM (brak jest nawet nazw tych klas, stąd w nawiasach proponuje się nazwy robocze), choć jego użycie przez autora nie jest odosobnione.

Jak łatwo się domyślić, klasy typu ViewViewModel udostępniają obiekty klas typu ModelViewModel, przy czym w zależności od rodzaju obsługiwanego interfejsu użytkownika, mogą udostępniać pojedynczy obiekt klasy typu ModelViewModel lub całą ich listę. Bowiem pełnią one często rolę pomocniczą do klas typu ModelViewModel, udostępniając operacje takie jak dodawanie i usuwanie elementów, aby klasy typu ModelViewModel mogły ograniczać się do „tłumaczenia” klas modelu na interfejs użytkownika.

Jedyną wadą stosowania tego podziału jest konieczność posiadania dwóch klas warstwy ViewModel, gdzie czasem wystarczyłaby jedna, co prowadzi niekiedy do powstania dużej liczby klas. Wydaje się jednak, że jest to niska cena, wobec uzyskania zgodności z zasadą pojedynczej odpowiedzialności.

Jak zapewne czytelnik pamięta, warstwa Model jest całkowicie przenośna. Ciekawym wobec tego, nieomówionym jeszcze tematem, jest przenośność warstwy ViewModel. Spróbujmy odpowiedzieć sobie na pytanie: czy warstwa ViewModel może być przenoszona między platformami?

Można wyobrazić sobie, że na każdą platformę, na którą dostarczana będzie aplikacja, przeznaczona będzie osobna implementacja warstwy ViewModel. Taka zasada jest logiczna, gdy różnice w interfejsie użytkownika pomiędzy platformami są znaczące (np. strona internetowa w porównaniu do aplikacji na telefon komórkowy). Z drugiej strony, gdy różnice są relatywnie niewielkie (np. aplikacja na system Windows w porównaniu do aplikacji na system Mac OS), można wyobrazić sobie zastosowanie wspólnej implementacji warstwy ViewModel na obu platformach, co znakomicie przyczyniłoby się do zwiększenia łatwości budowy aplikacji, przeznaczonych na wiele platform.

No Responses to “Wzorzec architektoniczny Model View ViewModel (1/2)”

Kanał RSS z komentarzami do tego wpisu. TrackBack URL

Leave a Response