Typowe wzorce modularyzacji
Zadbaj o dobrą organizację dzięki kolekcji
Zapisuj i kategoryzuj treści zgodnie ze swoimi preferencjami.
Nie ma jednej strategii modularyzacji, która sprawdzi się we wszystkich projektach. Z powodu
Elastyczność Gradle. Nie ma ograniczeń, co do tego,
i planować projekt. Na tej stronie znajduje się omówienie niektórych ogólnych reguł i typowych reguł
które możesz wykorzystać przy tworzeniu wielomodułowych aplikacji na Androida.
Zasada wysokiej spójności i niskiego sprzężenia
Jednym ze sposobów do scharakteryzowania modułowej bazy kodu jest użycie łączenia.
i spójność. Łączność mierzy stopień, w jakim moduły
zależy od siebie. Spójność określa w tym kontekście, jak elementy
pod względem funkcjonalności. Ogólnie rzecz biorąc, staraj się
niski współczynnik sprzężenia i wysoka spójność:
Niski współczynnik sprzężenia oznacza, że moduły powinny być jak najbardziej niezależne od
się poszczególne moduły, dzięki czemu zmiany w jednym module nie będą miały żadnego lub minimalnego wpływu
innych modułów. Moduły nie powinny wiedzieć, jak działają
innych modułów.
Wysoka spójność oznacza, że moduły powinny zawierać zbiór kodu, który
działa jak system. Powinni mieć jasno określone obowiązki i powinny
w zakresie wiedzy
z konkretnej dziedziny. Rozważ skorzystanie z przykładowego e-booka
aplikacji. Łączenie kodu związanego z książką i kodem płatności może być nieodpowiednie
w jednym module, ponieważ są to 2 różne domeny funkcjonalne.
.
Typy modułów
Sposób porządkowania modułów zależy głównie od architektury aplikacji. Poniżej
to typowe typy modułów, które możesz wprowadzić do aplikacji, jednocześnie śledząc
zalecaną architekturę aplikacji.
Moduły danych
Moduł danych zwykle zawiera repozytorium, źródła danych i klasy modelu.
3 główne zadania modułu danych to:
Uwzględnij wszystkie dane i logikę biznesową określonej domeny: każde dane
powinien być odpowiedzialny za obsługę danych, które reprezentują
w Twojej domenie. Może obsłużyć wiele typów danych, o ile są one ze sobą powiązane.
Udostępnij repozytorium jako zewnętrzny interfejs API: publiczny interfejs API danych.
powinien być repozytorium, ponieważ odpowiada on za udostępnianie danych
reszta aplikacji.
Ukryj wszystkie szczegóły implementacji i źródła danych z zewnątrz:
Źródła danych powinny być dostępne tylko dla repozytoriów z tego samego modułu.
Z zewnątrz pozostają ukryte. Możesz to egzekwować, korzystając z
Słowo kluczowe dotyczące widoczności: private lub internal.
.
Rysunek 1. Przykładowe moduły danych i ich zawartość.
Moduły funkcji
Funkcja to wyizolowana część funkcjonalności aplikacji, która zwykle odpowiada
ekranu lub serii ściśle powiązanych ze sobą ekranów, takich jak rejestracja lub płatność.
przepływu danych. Jeśli Twoja aplikacja ma pasek nawigacyjny u dołu, prawdopodobnie każde miejsce docelowe
jest funkcją.
Rysunek 2. Każdą kartę tej aplikacji można zdefiniować jako funkcję.
Funkcje są powiązane z ekranami lub miejscami docelowymi w aplikacji. Dlatego
prawdopodobnie ma powiązany interfejs użytkownika i ViewModelobsługujące ich logikę
i stanu. Nie trzeba ograniczać ich do pojedynczego wyświetlenia ani
miejsce docelowe nawigacji. Moduły funkcji zależą od modułów danych.
Rysunek 3. Przykładowe moduły funkcji i ich zawartość.
Moduły aplikacji
Moduły aplikacji są punktem wejścia do aplikacji. Zależą one od funkcji
i zwykle zapewniają nawigację główną. Można skompilować jeden moduł aplikacji
w kilku różnych plikach binarnych dzięki tworzeniu wariantów.
Rysunek 4. Wykres zależności między modułami smakowymi produktu i *demonstracyjnym* oraz *pełnym*.
Jeśli Twoja aplikacja jest kierowana na kilka typów urządzeń, takich jak automatyczne, Wear czy TV, zdefiniuj moduł aplikacji dla każdego z nich. Pomaga to rozdzielić poszczególne platformy
zależności.
Rysunek 5. Wykres zależności aplikacji na Wear.
Typowe moduły
Popularne moduły, nazywane też modułami podstawowymi, zawierają kod, który jest stosowany w innych modułach
często używanych. Zmniejszają one nadmiarowość i nie reprezentują żadnej konkretnej warstwy
architektury aplikacji. Oto przykłady typowych modułów:
Moduł interfejsu: jeśli wykorzystujesz niestandardowe elementy interfejsu lub wymyślne elementy marki w
warto rozważyć umieszczenie kolekcji widżetów w module
pod kątem wszystkich funkcji do ponownego wykorzystania. Może to pomóc w ujednoliceniu interfejsu
różne funkcje. Jeśli na przykład układy tematyczne są scentralizowane,
bolesna zmiana nazwy marki.
Moduł Analytics: śledzenie jest często uzależnione od wymagań biznesowych, takich jak
przy niewielkich wyobrażeniach o architekturze oprogramowania. Moduły śledzące Analytics są
często są wykorzystywane w wielu niepowiązanych ze sobą komponentach. Jeśli tak jest w Twoim przypadku,
zalecanym modułem jest specjalny moduł analityczny.
Moduł sieci: jeśli wiele modułów wymaga połączenia sieciowego, może
rozważ utworzenie modułu przeznaczonego dla klienta HTTP. Jest
szczególnie przydatne, gdy Twój klient wymaga niestandardowej konfiguracji.
Moduł narzędziowy: narzędzia (nazywane też funkcjami pomocniczymi) są zwykle niewielkimi elementami.
kodu, który będzie wielokrotnie wykorzystywany w aplikacji. Przykłady narzędzi:
narzędzie do testowania, funkcję formatowania waluty, walidator poczty e-mail
.
Moduły testowe
Moduły testowe to moduły na Androida, które są używane tylko do testowania.
Moduły zawierają kod testowy, zasoby testowe i zależności testowe, które są tylko
są wymagane do uruchamiania testów i nie są potrzebne w czasie działania aplikacji.
Moduły testowe są tworzone w celu oddzielania kodu określonego do testów od kodu głównego
aplikacji, co ułatwia zarządzanie kodem modułu i jego utrzymywanie.
Przypadki użycia modułów testowych
Poniższe przykłady pokazują sytuacje, w których zaimplementowanie modułów testowych
mogą być szczególnie korzystne:
Wspólny kod testu: jeśli w projekcie masz wiele modułów i niektóre
kod testu odnosi się do więcej niż jednego modułu, możesz utworzyć test
do udostępnienia kodu. Zmniejsza to liczbę duplikatów i ułatwia test
i łatwiej zarządzać kodem. Udostępniony kod testu może zawierać klasy narzędzi
takich jak niestandardowe asercje i dopasowania, a także dane testowe, takie jak
symulowanych odpowiedzi JSON.
Konfiguracje kompilacji oczyszczającej: moduły testowe umożliwiają uzyskanie bardziej przejrzystej
konfiguracje, ponieważ mogą mieć własny plik build.gradle. Ty nie
zaśmiecać plik build.gradle modułu aplikacji konfiguracjami, które są
tylko w przypadku testów.
Testy integracji: moduły testowe mogą służyć do przechowywania danych o integracji.
które służą do testowania interakcji między różnymi częściami aplikacji,
uwzględniające interfejs użytkownika, logikę biznesową, żądania sieciowe i zapytania do bazy danych.
Aplikacje na dużą skalę: moduły testowe są szczególnie przydatne w przypadku:
dużych aplikacji ze złożonymi bazami kodu i wieloma modułami. W takim
W takich przypadkach moduły testowe mogą pomóc w ulepszaniu organizacji kodu i łatwiejszej konserwacji.
.
Rysunek 6. Moduły testowe można wykorzystać do izolowania modułów, które w innym przypadku byłyby od siebie zależne.
Komunikacja między modułami
Moduły rzadko występują w całkowitym rozdzieleniu i często bazują na innych modułach oraz
w kontakcie z klientami. Ważne jest, aby połączenie było niskie, nawet jeśli moduły
współpracować i często wymieniać się informacjami. Czasami bezpośrednia
komunikacja między dwoma modułami jest lub
z ograniczeniami architektury. Może się to też okazać niemożliwe, np. w przypadku cyklicznego
zależności.
Rysunek 7. Bezpośrednia, dwukierunkowa komunikacja między modułami jest niemożliwa z powodu zależności cyklicznych. Moduł zapośredniczenia jest niezbędny do skoordynowania przepływu danych między 2 innymi niezależnymi modułami.
Aby rozwiązać ten problem, możesz zastosować trzeci moduł zapośredniczenia.
między dwoma innymi modułami. Moduł mediacji może nasłuchiwać wiadomości od
i przekazywać je dalej w razie potrzeby. W naszej przykładowej aplikacji proces płatności
musi wiedzieć, którą książkę kupić, mimo że wydarzenie ma swoje początki
na osobnym ekranie, który jest częścią innej funkcji. W tym przypadku parametr
mediator to moduł będący właścicielem wykresu nawigacji (zwykle jest to moduł aplikacji).
W tym przykładzie używamy nawigacji, aby przekazywać dane z funkcji Dom
za pomocą komponentu Nawigacja.
navController.navigate("checkout/$bookId")
Miejsce docelowe płatności otrzymuje identyfikator książki jako argument, którego używa do
pobrać informacje o książce. Możesz użyć nicka zapisanego stanu, aby:
możesz pobrać argumenty nawigacyjne w elemencie ViewModel funkcji docelowej.
classCheckoutViewModel(savedStateHandle:SavedStateHandle,…):ViewModel(){valuiState:StateFlow<CheckoutUiState>=savedStateHandle.getStateFlow<String>("bookId","").map{bookId->
// produce UI state calling bookRepository.getBook(bookId)}…}
Nie należy przekazywać obiektów jako argumentów nawigacyjnych. Zamiast tego użyj prostych identyfikatorów
dzięki którym funkcje mogą uzyskiwać dostęp do wybranych zasobów z warstwy danych i wczytywać je z poziomu warstwy danych.
W ten sposób unikniesz problemów z pojedynczym źródłem danych.
tej zasady.
W przykładzie poniżej oba moduły funkcji są zależne od tego samego modułu danych. Ten
pozwala zminimalizować ilość danych, których potrzebuje moduł mediatora
do przodu i zachowuje połączenie między modułami. Zamiast przekazywać
, moduły powinny wymieniać identyfikatory podstawowe i wczytywać zasoby z
modułu udostępniania danych.
Rysunek 8. 2 moduły funkcji korzystające z modułu udostępnianych danych.
Odwrócenie zależności
Odwrócenie zależności występuje wtedy, gdy uporządkujesz kod w taki sposób, aby abstrakcja
co nie jest związane z konkretnym wdrożeniem.
abstrakcja: umowa określająca, jak komponenty lub moduły w
współdziałają ze sobą. Moduły abstrakcyjne definiują interfejs API
i zawierać interfejsy i modele.
Konkretna implementacja: moduły zależne od modułu abstrakcji
i wdrożyć zachowanie abstrakcji.
Moduły korzystające z zachowania zdefiniowanego w module abstrakcji powinny
zależą od samej abstrakcji, a nie od konkretnych implementacji.
Rysunek 9. Zamiast modułów wysokiego poziomu bazujących bezpośrednio na modułach niskiego poziomu zależne od modułu abstrakcji.
Przykład
Wyobraź sobie moduł funkcji, który wymaga do działania bazy danych. Moduł funkcji nie jest
związane ze sposobem wdrożenia bazy danych, np. lokalnej bazy danych sal lub
zdalną instancję Firestore. Wystarczy, że będzie przechowywać i odczytywać dane aplikacji.
W tym celu moduł funkcji bazuje na module abstrakcji,
niż w konkretnej implementacji baz danych. Ta abstrakcja definiuje
API bazy danych. Innymi słowy, określa reguły interakcji z
w bazie danych. Dzięki temu moduł funkcji może używać dowolnej bazy danych bez konieczności
i poznać szczegóły implementacji.
Moduł konkretnych implementacji zapewnia rzeczywiste wdrożenie
Interfejsy API zdefiniowane w module abstrakcji. W tym celu implementacja
zależy też od modułu abstrakcji.
Wstrzykiwanie zależności
Być może zastanawiasz się, jak moduł funkcji jest połączony
z modułu wdrożenia. Wynik to Wstrzykiwanie zależności. Funkcja
moduł nie tworzy bezpośrednio wymaganej instancji bazy danych. Zamiast tego
określa potrzebne zależności. Zależności te są następnie podawane
zewnętrznie, zwykle w module aplikacji.
Korzyści z oddzielenia interfejsów API i ich implementacji są następujące:
Wymienność: dzięki wyraźnemu oddzieleniu interfejsu API od implementacji
modułów, można opracować wiele implementacji dla tego samego interfejsu API oraz przełączać
bez konieczności zmiany kodu korzystającego z tego interfejsu. Może to być
Jest to szczególnie przydatne w sytuacjach, gdy chcesz udostępniać różne treści
umiejętności lub zachowania użytkowników w różnych kontekstach. Na przykład makiet
do testowania, a nie do rzeczywistej implementacji w środowisku produkcyjnym.
Rozłączanie: oznacza to, że moduły korzystające z abstrakcyjnych funkcji nie
zależy od konkretnej technologii. Jeśli zmienisz nazwę bazy danych z
W przyszłości dostęp do Firestore będzie łatwiejszy, ponieważ zmiany
występują w konkretnym module wykonującym zadanie (moduł implementacji), i nie
wpływa na inne moduły
za pomocą interfejsu API bazy danych.
Testowalność: oddzielenie interfejsów API od ich implementacji może
i umożliwiają przeprowadzanie testów. Możesz pisać przypadki testowe na podstawie umów dotyczących interfejsu API. Dostępne opcje
stosują też różne implementacje do testowania różnych scenariuszy i przypadków skrajnych,
również na przykładach implementacji.
Większa wydajność kompilacji: gdy oddzielisz interfejs API od jego
wdrożenia w różnych modułach, zmian w implementacji
nie wymusza na systemie kompilacji ponownej kompilacji modułów w zależności od
API. Przyspiesza to tworzenie i zwiększa produktywność,
zwłaszcza w dużych projektach, w których czas tworzenia może być istotny.
Kiedy należy rozdzielać
Warto oddzielić interfejsy API od ich implementacji
w następujących przypadkach:
Zróżnicowane możliwości: jeśli możesz wdrożyć wybrane części swojego systemu
klarowny interfejs API umożliwia wymianę różnych
implementacji. Możesz na przykład mieć system renderowania korzystający z trybu OpenGL.
albo systemu rozliczeniowego Vulkan, który współpracuje z Google Play
API.
Wiele aplikacji: jeśli tworzysz wiele aplikacji za pomocą
wspólne możliwości dla różnych platform, można definiować wspólne interfejsy API
opracować konkretne implementacje dla każdej platformy.
Niezależne zespoły: rozdzielenie umożliwia różnym programistom lub zespołom
pracować nad różnymi częściami bazy kodu w tym samym czasie. Deweloperzy powinni się skupić
poznawanie umów dotyczących interfejsu API i prawidłowe korzystanie z nich. Nie muszą
szczegóły implementacji innych modułów.
Duża baza kodu: jeśli baza kodu jest duża lub złożona, rozdzielenie interfejsu API.
co sprawia, że kod jest łatwiejszy do zarządzania. Umożliwia to naruszenie
bazy kodu na bardziej szczegółowe, zrozumiałe i łatwe w utrzymaniu jednostki.
Jak wdrożyć?
Aby wdrożyć odwrócenie zależności, wykonaj te czynności:
Utwórz moduł abstrakcji: ten moduł powinien zawierać interfejsy API (interfejsy).
i modele), które definiują działanie danej cechy.
Utwórz moduły implementacji: moduły implementacji powinny opierać się na
API i zaimplementować zachowanie abstrakcji.
Rysunek 10. Moduły implementacji zależą od modułu abstrakcji..
Uzależnić moduły wysokiego poziomu od modułów abstrakcyjnych: zamiast
w zależności od konkretnej implementacji, zadbaj o to, by Twoje moduły
modułów abstrakcji. Moduły ogólne nie wymagają wdrożenia
potrzebują tylko umowy (API).
Rysunek 11. Moduły ogólne opierają się na abstrakcjach, a nie na implementacji..
Jak wspomnieliśmy na początku, nie ma jednego słusznego sposobu
aplikacja wielomodułowa. Tak jak jest wiele architektur oprogramowania,
na wiele sposobów
modularyzacji aplikacji. Mimo wszystko to ogólne
które pomogą Ci zwiększyć czytelność i łatwiejszy w utrzymaniu kod
możliwy do przetestowania.
Zadbaj o spójność konfiguracji
W każdym module są związane z konfiguracją. Jeśli liczba modułów
osiąga określony próg, zarządzanie spójną konfiguracją staje się
do wyzwania. Ważne jest na przykład, aby moduły używały zależności tego samego
wersji. Jeśli musisz zaktualizować dużą liczbę modułów,
aby tylko zestawić
wersji zależnej, oznacza to nie tylko wysiłek, ale także miejsce na potencjalne
błędów. Aby rozwiązać ten problem, możesz użyć jednego z narzędzi Gradle do
scentralizuj konfigurację:
Katalogi wersji to lista zależności bezpiecznych typów.
wygenerowanych przez Gradle podczas synchronizacji. To miejsce, w którym możesz zadeklarować
zależności i jest dostępny dla wszystkich modułów w projekcie.
Publiczny interfejs modułu powinien być minimalny i zawierać tylko
podstawowe funkcje. Żadne szczegóły implementacji nie powinny wyciekać na zewnątrz. Zakres
w najkrótszym możliwym zakresie. Użyj aplikacji private lub internal od Kotlina
i określić zakres widoczności modułu, aby stał się prywatny. Podczas deklarowania
zależności w module, użyj ścieżki implementation zamiast api. To ostatnie
naraża użytkowników modułu na pośrednie zależności. Zastosowanie
wdrożenie może skrócić czas kompilacji, ponieważ zmniejsza liczbę modułów
które wymagają rekonstrukcji.
Preferuj Kotlin & Moduły Java
Android Studio obsługuje 3 podstawowe typy modułów:
Moduły aplikacji są punktem wejścia do aplikacji. Mogą zawierać
kodu źródłowego, zasobów i zasobów oraz AndroidManifest.xml. Dane wyjściowe funkcji
modułem aplikacji jest Android App Bundle (Android App Bundle) lub Android Application Package;
(plik APK).
Moduły biblioteki mają tę samą treść co moduły aplikacji. Są
używane przez inne moduły Androida jako zależność. Dane wyjściowe modułu biblioteki
to archiwum Androida (AAR) są identyczne pod względem konstrukcji z modułami aplikacji, ale
są skompilowane w plik Android Archive (AAR), którego mogą później używać inne
jako zależność. Moduł biblioteki umożliwia
obejmować i wykorzystywać ponownie tę samą logikę i zasoby w wielu modułach aplikacji.
Biblioteki Kotlin i Java nie zawierają żadnych zasobów ani zasobów Androida.
plików manifestu.
Moduły na Androida są płatne, więc lepiej użyć
Kotlin czy Java.
Treść strony i umieszczone na niej fragmenty kodu podlegają licencjom opisanym w Licencji na treści. Java i OpenJDK są znakami towarowymi lub zastrzeżonymi znakami towarowymi należącymi do firmy Oracle lub jej podmiotów stowarzyszonych.