Wzorce architektury Androida Część 1: Model-View-Controller
Rok temu, kiedy większość obecnego zespołu Androida rozpoczęła pracę w upday, aplikacja była daleka od bycia solidną, stabilną aplikacją, którą chcieliśmy, aby była. Próbowaliśmy zrozumieć, dlaczego nasz kod był w tak złym stanie i znaleźliśmy dwóch głównych winowajców: ciągłe zmiany UI i brak architektury, która wspierałaby elastyczność, której potrzebowaliśmy. Aplikacja była już po czwartym przeprojektowaniu w ciągu sześciu miesięcy. Wybrany wzorzec projektowy wydawał się być Model-View-Controller, ale wtedy był już „mutantem”, daleko od tego, jak powinno być.
Odkryjmy razem, czym jest wzorzec Model-View-Controller; jak był stosowany w Androidzie przez lata; jak powinien być stosowany, aby zmaksymalizować testowalność; i niektóre z jego zalet i wad.
W świecie, w którym logika interfejsu użytkownika ma tendencję do zmiany częściej niż logika biznesowa, pulpit i Web deweloperzy potrzebowali sposobu na oddzielenie funkcjonalności interfejsu użytkownika. Wzorzec MVC był ich rozwiązaniem.
- Model – warstwa danych, odpowiedzialna za zarządzanie logiką biznesową i obsługę API sieciowego lub bazodanowego.
- Widok – warstwa UI – wizualizacja danych z Modelu.
- Kontroler – warstwa logiki, dostaje powiadomienia o zachowaniu użytkownika i aktualizuje Model w razie potrzeby.
Oznacza to więc, że zarówno Kontroler jak i Widok zależą od Modelu: Kontroler od aktualizacji danych, Widok od pobierania danych. Ale, co najważniejsze dla devów desktopowych i webowych w tamtym czasie: Model był oddzielony i mógł być testowany niezależnie od UI. Pojawiło się kilka wariantów MVC. Najbardziej znane są te związane z tym, czy Model jest pasywny czy aktywnie powiadamia o tym, że się zmienił. Oto więcej szczegółów:
Model pasywny
W wersji Model pasywny, Kontroler jest jedyną klasą, która manipuluje Modelem. W oparciu o działania użytkownika, Kontroler musi modyfikować Model. Po tym jak Model zostanie zaktualizowany, Kontroler powiadomi Widok, że on również potrzebuje aktualizacji. W tym momencie, Widok zażąda danych z Modelu.
Model aktywny
Dla przypadków, gdy Kontroler nie jest jedyną klasą modyfikującą Model, Model potrzebuje sposobu na powiadomienie Widoku i innych klas o aktualizacjach. Osiąga się to za pomocą wzorca obserwatora. Model zawiera kolekcję obserwatorów, którzy są zainteresowani aktualizacjami. Widok implementuje interfejs obserwatora i rejestruje się jako obserwator Modelu.
Za każdym razem, gdy Model będzie się aktualizował, będzie również iterował przez kolekcję obserwatorów i wywoływał metodę update
. Implementacja tej metody w widoku spowoduje następnie wywołanie żądania najnowszych danych z Modelu.
Model-Widok-Kontroler w Androidzie
W okolicach 2011 roku, kiedy Android zaczął stawać się coraz bardziej popularny, naturalnie pojawiły się pytania o architekturę. Ponieważ MVC był jednym z najbardziej popularnych wzorców UI w tym czasie, deweloperzy próbowali zastosować go również w Androidzie.
Jeśli wyszukasz na StackOverflow pytania typu „Jak zastosować MVC w Androidzie”, jedna z najpopularniejszych odpowiedzi stwierdzała, że w Androidzie, Aktywność jest zarówno Widokiem jak i Kontrolerem. Patrząc wstecz, brzmi to prawie jak szaleństwo! Ale w tym momencie, główny nacisk kładziono na testowalność Modelu i zazwyczaj wybór implementacji dla Widoku i Kontrolera był zależny od platformy.
How Should MVC Be Applied in Android
W dzisiejszych czasach, pytanie jak zastosować wzorce MVC ma odpowiedź, która jest łatwiejsza do znalezienia. Działania, Fragmenty i Widoki powinny być Widokami w świecie MVC. Kontrolery powinny być oddzielnymi klasami, które nie rozszerzają ani nie używają żadnej klasy Androida, i to samo dla Modeli.
Jeden problem pojawia się podczas łączenia Kontrolera z Widokiem, ponieważ Kontroler musi powiedzieć Widokowi, aby się zaktualizował. W pasywnej architekturze Model MVC, Kontroler musi posiadać referencję do Widoku. Najprostszym sposobem na zrobienie tego, jednocześnie skupiając się na testowaniu, jest posiadanie interfejsu BaseView, który rozszerzałby Aktywność/Fragment/Widok. Tak więc, kontroler miałby referencję do BaseView.
Wady
Wzorzec Model-View-Controller bardzo wspiera separację obaw. Ta zaleta nie tylko zwiększa testowalność kodu, ale także ułatwia jego rozszerzanie, pozwalając na dość łatwą implementację nowych funkcji.
Klasy Model nie mają żadnych odwołań do klas Androida i dlatego są proste do testowania jednostkowego. Kontroler nie rozszerza ani nie implementuje żadnych klas Androida i powinien mieć referencję do klasy interfejsu Widoku. W ten sposób, testowanie jednostkowe Kontrolera jest również możliwe.
Jeśli Widoki respektują zasadę pojedynczej odpowiedzialności, wtedy ich rolą jest tylko aktualizowanie Kontrolera dla każdego zdarzenia użytkownika i tylko wyświetlanie danych z Modelu, bez implementacji jakiejkolwiek logiki biznesowej. W tym przypadku, testy UI powinny być wystarczające do pokrycia funkcjonalności Widoku.
The View Depends On The Controller And On The Model
Zależność Widoku od Modelu zaczyna być minusem w złożonych widokach. W celu zminimalizowania logiki w Widoku, Model powinien być w stanie zapewnić testowalne metody dla każdego elementu, który ma być wyświetlany. W aktywnej implementacji Modelu, to wykładniczo zwiększa liczbę klas i metod, biorąc pod uwagę, że Obserwatory dla każdego typu danych byłyby wymagane.
Biorąc pod uwagę, że Widok zależy zarówno od Kontrolera jak i Modelu, zmiany w logice UI mogą wymagać aktualizacji w kilku klasach, zmniejszając elastyczność wzorca.
Kto zajmuje się logiką UI?
Zgodnie z wzorcem MVC, Kontroler aktualizuje Model, a Widok pobiera dane do wyświetlenia z Modelu. Ale kto decyduje o tym, jak wyświetlić dane? Czy jest to Model czy Widok? Weźmy pod uwagę następujący przykład: mamy User
, z imieniem i nazwiskiem. W Widoku musimy wyświetlić nazwę użytkownika jako „Lastname, Firstname” (np. „Doe, John”).
Jeśli rolą Modelu jest tylko dostarczenie „surowych” danych, oznacza to, że kod w Widoku byłby:
String firstName = userModel.getFirstName();
String lastName = userModel.getLastName(); nameTextView.setText(lastName + ", " + firstName)
Więc oznacza to, że to Widok byłby odpowiedzialny za obsługę logiki UI. Ale to czyni logikę UI niemożliwą do przetestowania jednostkowego.
Innym podejściem jest posiadanie Modelu eksponującego tylko dane, które muszą być wyświetlane, ukrywając wszelką logikę biznesową przed Widokiem. Ale wtedy kończymy z Modelami, które obsługują zarówno logikę biznesową jak i UI. Byłoby to testowalne jednostkowo, ale wtedy Model kończy się, niejawnie będąc zależnym od Widoku.
String name = userModel.getDisplayName(); nameTextView.setText(name);
Wniosek
W początkach Androida wzorzec Model-Widok-Kontroler wydawał się mylić wielu programistom i prowadził do kodu, który był trudny, jeśli nie niemożliwy do przetestowania jednostkowego.
Zależność Widoku od Modelu i posiadanie logiki w Widoku skierowało naszą bazę kodową do stanu, z którego niemożliwe było odzyskanie bez całkowitego refaktoryzacji aplikacji. Jakie było nowe podejście w architekturze i dlaczego? Dowiedz się czytając ten wpis na blogu.