Podobnie jak wielu początkujących programistów przede mną tak i ja swoje pierwsze aplikacje tworzyłem bez żadnej architektury, czy też choćby planu jej późniejszego zaimplementowania. Na samym początku uznałem, że nie jest mi to do niczego potrzebne (albo miałem za mało wiedzy żeby dokładnie zrozumieć o co chodzi).  Bardzo szybko przekonałem się jednak, że architektura się przyda 😝. Natchnęło mnie w momencie, kiedy moja aplikacja zaczęła opierać się w głównej mierze o klasy określane w języku angielskim jako „God class” (tłumaczenie na język polski jest chyba zbędne). Padło akurat na projekt dla iOS, więc obiekty te były reprezentowane przez View Controllers. Klasa dla każdego widoku zajmowała się właściwie wszystkim co możliwe, od pobierania danych z serwera / lokalnej bazy danych, aż po ich prezentację na ekranie. Do dzisiaj przechodzą mnie dreszcze jak sobie o tym przypomnę 🤪. Wystarczyło jednak trochę poszperać i okazało się, że istnieje coś takiego jak wzorzec architektury o nazwie Model View Controller (MVC). W tamtej chwili doznałem olśnienia.

 

 

Wzorzec MVC przybliżę Wam w osobnym wpisie, ponieważ ten poświęcony jest wzorcowi, który jest do niego zbliżony, ale stosowany jest głównie przez deweloperów aplikacji na Androida. Nosi on nazwę Model View Presenter (MVP) i zawsze jak staram się komuś o nim opowiedzieć to zaczynam mieszać go z MVC 😩. Tak się akurat złożyło, że ostatnio wraz z kilkoma innymi osobami rozpoczęliśmy tworzenie projektu open source, który w założeniach ma ułatwić przygarnianie zwierzaków ze schronisk. Jesteśmy na etapie wyboru architektury i wstępnie zdecydowaliśmy się właśnie na Model View Presenter. W ramach jednego z zadań, kilka osób zobowiązało się stworzenia prostych przykładów, na podstawie których reszta zespołu ma rozpocząć implementowanie MVP w całym projekcie. Ja podzielę się z Wami swoją pracą 😏.

Zanim jednak przejdziemy do kodowania to jeszcze kilka słów o tym dlaczego jakakolwiek architektura jest nam w ogóle potrzebna. Najprościej rzecz ujmując, korzystając z odpowiedniej architektury wszystko w naszej aplikacji będzie znajdowało się na swoim miejscu. Z uwagi na modułową budowę dużo łatwiej będzie nam pisać testy jednostkowe pokrywające większą ilość kodu, debuggować poszczególne obiekty (mniej funkcji w jednym miejscu), łatwiej śledzić tworzoną logikę, a także (co bardzo istotne) ułatwimy pracę członkom naszego zespołu, którzy w pewnym momencie również zaczną wykorzystywać napisany przez nas kod. W skrócie – nie uda nam się stworzyć wydajnej aplikacji bez odpowiedniej architektury 🤓.

Nie twierdzę oczywiście, że MVP jest najlepszym / jedynym możliwym rozwiązaniem, ponieważ bardzo wiele zależy od potrzeb samego projektu oraz preferencji zespołu. Oprócz MVP można skorzystać jeszcze ze wspomnianego wyżej MVC, czyt też takiego wzorca jak MVVM (Mode-View-View-Model). Nasz zespół zdecydował się na wprowadzenie akurat MVP, ponieważ kilka osób miało z nim już jakieś doświadczenie. Dla mnie był on jednak zupełną nowością, ponieważ różni się on jednak od stosowanego przeze mnie w projektach na iOS wzroca MVC.

Model View Presenter jest wzorcem polecanym przez samego Googla, chociaż akurat oni kładą większy nacisk na MVVM w związku z wprowadzonymi przez nich całkiem niedawno android architecture components. Spójrzcie sobie na poniższą grafikę, która stanowi reprezentację wzorca MVP:

 

Źródło: Informatech CR Blog

 

Tak prezentuje się odpowiedzialność dla poszczególnych komponetów:

View – pasywny interfejs, który powinien zajmować się wyłącznie wyświetlaniem danych oraz przekazywaniem interakcji użytkownika z aplikacją do Presentera (w skład tych interakcji wchodzi nie tylko dotyk – wliczą się w to również np. przechylenie telefonu). W architekturze Androida rolę widoków pełnią Acitvities, Fragments oraz obiekty dziedziczące po klasie View (czyli np. takie Edit Text). View powinien być pasywny – mówi się, że ma być „najgłupszy” jak to tylko możliwe. Oznacza to, że nie powinna pojawiać się w nim żadna logika biznesowa. Natomiast wszystkie funkcje związane z wyświetlaniem widoków powinny znajdować się właśnie po stronie obiektu View – wliczamy w to również animacje.

Model – warstwa do kontrolowania jak dane są tworzone, składowane i modyfikowane. To właśnie tutaj umieszczane są obiekty stanowiące na przykład model pojedynczego zwierzaka (tak jak w aplikacji, którą piszemy razem z zespołem), czy też obiekty odpowiedzialne za współpracę z bazą danych lub zdalnym serwerem.

Presenter – w nim umieszczamy naszą logiką biznesową. Pełni on funkcję mediatora pomiędzy komponentem View oraz Model. Pobiera dane z Modelu a następnie przekazuje do wyświetlenia komponentowi View. Jest również odpowiedzialny za przetwarzanie zdarzeń wywołanych przez użytkownika, wchodzącego w interakcje z warstwą View. Presenter powinien być powiązany wyłącznie z jednym obiektem View. Powinien być on również całkowicie niezależny od bibliotek Androida. Jeżeli na przykład potrzebny nam będzie obiekt Context w celu skorzystania z SharedPreferences to oznacza, że powinniśmy dany kod umieścić w warstwie Model. Właściwa implementacja komponentu Presenter pomaga również w zarządzaniu zasobami aplikacji z wykorzystaniem kilku wątków. Odpytanie bazy danych może zostać wykonane asynchronicznie, a rezultat przekazany za pomocą metody callback z warstwy Model do warstwy Presenter.

To tyle samej teorii. Zobaczmy jak to będzie wyglądało w praktyce w oparciu o bardzo prosty przykładu. W naszej aplikacji będziemy korzystali z klasy, która będzie modelowała naszego zwierzaka (np. psa). Klasa ta będzie należała do warstwy Model:

 

 

Teraz dodamy interfejs, który będzie pełnił rolę kontraktu pomiędzy View oraz Presenter. Umieszczanie tych zależności w jednym miejscu pomoże w utrzymaniu przejrzystości kodu:

 

 

Dodamy klasę bazową dla naszego Presentera, tak aby każda klasa pochodna implementowała dwie podstawowe funkcje:

 

 

Funkcje w klasie bazowej będą odpowiadały za określanie relacji pomiędzy obiektem Presenter a obiektem View. Za ich pomocą dwa obiekty będą mogły nawiązać współpracę lub ją zakończyć. Dodatkowa funkcja pozwoli na sprawdzenie, czy widok jest aktualnie powiązany z Presenterem.

Skoro mamy już interfejs i klasę bazową dla Presentera to musimy teraz stworzyć obiekt, który będzie je implementował:

 

 

Na przykładzie funkcji getDogName() możecie, zobaczyć jak zachowuje się Presenter. Pobiera on dane za pomocą warstwy Model. W tym wypadku reprezentowana jest ona przez obiekt Dog, ale w bardziej złożonych aplikacjach w jego miejsce wstawicie obiekt odpowiedzialny na przykład za współpracę z bazą danych. Presenter dokonuje odpowiednich operacji, a następnie przekazuje dane do obiektu View. Obiekt View ma je tylko wyświetlić, dlatego utworzenie obiektu dogName konieczne było w obiekcie Presenter.

Implementacją dla warstwy View będzie w tym wypadku Activity, ale równie dobrze może być to obiekt typu Fragment:

 

 

I to już koniec. Cały powyższy kod jest tylko bardzo prostym przykładem jak MVP może zostać zaimplementowane, jednak wydaje mi się, że jest on dobrym wstępem do dalszych rozważań. Właściwa implementacja wzorca Model View Presenter wymaga czasu i wielu prób. Nie ma jednej właściwej drogi, a jedynie kilka wytycznych, które wskazują właściwy kierunek. Z całą pewnością warto jednak podjąć odpowiedni wysiłek i oprzeć naszą aplikację o właściwą architekturę 😎.


 

Comments

  1. Mały błąd w BasePresenter

    public boolean isViewAttached(){
    return this.view == null;
    }

    powinno być :

    public boolean isViewAttached(){
    return this.view != null;
    }

    W przypadki jeżeli te pole będzie miało wartość null, ta metoda by zwróciła by true i wzbudziła zamieszanie dla osoby korzystającej z niej. :p

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *