Pattern di modularizzazione comuni
Mantieni tutto organizzato con le raccolte
Salva e classifica i contenuti in base alle tue preferenze.
Non esiste una singola strategia di modularizzazione adatta a tutti i progetti. A causa di
la natura flessibile di Gradle, esistono pochi vincoli su come
per organizzare un progetto. Questa pagina fornisce una panoramica di alcune regole generali e
pattern che puoi utilizzare durante lo sviluppo di app per Android multimodulo.
Principio di alta coesione e basso accoppiamento
Un modo per caratterizzare un codebase modulare consiste nell'utilizzare l'accoppiamento
e coesione. L'accoppiamento misura il grado di coinvolgimento dei moduli
dipendono l'uno dall'altro. La coesione, in questo contesto, misura il modo in cui gli elementi di una
sono correlate dal punto di vista funzionale. Come regola generale, devi cercare di
basso accoppiamento e coesione elevata:
Basso accoppiamento significa che i moduli dovrebbero essere il più indipendenti possibile
l'uno con l'altro, in modo che le modifiche a un modulo abbiano un impatto nullo o minimo sulle
e gli altri moduli. I moduli non devono conoscere il funzionamento interno di
altri moduli.
Elevata coesione significa che i moduli devono comprendere una raccolta di codice che
funge da sistema. Devono avere responsabilità chiaramente definite e rimanere
entro i limiti di determinate
conoscenze settoriali. Valuta un ebook di esempio
un'applicazione. Potrebbe essere inappropriato combinare il codice relativo al libro e al pagamento
nello stesso modulo, poiché sono due domini funzionali diversi.
di Gemini Advanced.
Tipi di moduli
Il modo in cui organizzi i moduli dipende principalmente dall'architettura dell'app. Inferiore
sono alcuni tipi comuni di moduli che potresti introdurre nella tua app mentre segui
la nostra architettura delle app consigliata.
Moduli di dati
Un modulo dati di solito contiene un repository, origini dati e classi di modelli. La
tre responsabilità principali di un modulo dati sono:
Incapsulare tutti i dati e la logica di business di un determinato dominio: ogni dato
dovrebbe essere responsabile della gestione dei dati che rappresentano un
dominio. Può gestire molti tipi di dati, purché correlati.
Esponi il repository come API esterna: l'API pubblica di un data warehouse
devono essere un repository in quanto sono responsabili dell'esposizione dei dati
per il resto dell'app.
Nascondere dall'esterno tutti i dettagli di implementazione e le origini dati:
Le origini dati devono essere accessibili solo dai repository dello stesso modulo.
Rimangono nascosti all'esterno. Puoi applicare in modo forzato questa funzionalità utilizzando
Parola chiave per la visibilità di private o internal.
di Gemini Advanced.
.
Figura 1. Esempi di moduli di dati e relativi contenuti.
Moduli delle funzionalità
Una funzione è una parte isolata della funzionalità di un'app che di solito corrisponde
a una schermata o a una serie di schermate strettamente correlate, come la registrazione o il pagamento
flusso di lavoro. Se la tua app dispone di una barra di navigazione in basso, è probabile che ogni destinazione
è una caratteristica.
Figura 2. Ogni scheda di questa applicazione può essere definita come una funzionalità.
Le funzionalità sono associate a schermate o destinazioni nella tua app. Pertanto,
è probabile che abbiano una UI associata e ViewModel per gestire la logica
e lo stato. Una singola funzionalità non deve essere necessariamente limitata a una singola visualizzazione o
destinazione di navigazione. I moduli delle funzionalità dipendono dai moduli di dati.
Figura 3. Esempi di moduli delle funzionalità e relativi contenuti.
Moduli dell'app
I moduli dell'app sono un punto di accesso all'applicazione. Dipendono dalla funzionalità
e di solito forniscono la navigazione root. È possibile compilare un singolo modulo dell'app
a una serie di programmi binari diversi grazie alle varianti della build.
Figura 4. Grafico delle dipendenze dei moduli versione *Demo* e *Completa* per le versioni dei prodotti.
Se la tua app ha come target più tipi di dispositivi, ad esempio auto, Wear OS o TV, definisci un modulo dell'app per ciascuno. Questo aiuta a separare le piattaforme
delle dipendenze.
Figura 5. Grafico delle dipendenze dell'app Wear.
Moduli comuni
I moduli comuni, noti anche come moduli principali, contengono codice che altri moduli
che usano spesso. Riducono la ridondanza e non rappresentano alcuno strato specifico in
l'architettura di un'app. Ecco alcuni esempi di moduli comuni:
Modulo UI: se utilizzi elementi UI personalizzati o un branding elaborato nelle tue
app, dovresti considerare di incorporare la raccolta di widget in un modulo
per poter riutilizzare tutte le caratteristiche. Questo può contribuire a rendere la UI coerente
diverse funzionalità. Ad esempio, se i temi sono centralizzati, puoi evitare
un doloroso refactoring quando avviene un rebranding.
Modulo Analytics: il monitoraggio è spesso dettato da requisiti aziendali con
poca considerazione per l'architettura software. I tracker di Google Analytics
spesso utilizzato in molti componenti non correlati. In questo caso, è possibile
un modulo dedicato per l'analisi dei dati.
Modulo di rete: quando molti moduli richiedono una connessione di rete, è possibile che
considera l'idea di creare un modulo dedicato alla fornitura di un client http. È
particolarmente utile quando il client richiede
una configurazione personalizzata.
Modulo di utilità: le utility, note anche come helper, sono di solito piccole parti
di codice riutilizzato nell'applicazione. Esempi di utilità includono
per il test, una funzione di formattazione della valuta, uno strumento di convalida email o un
operatore.
Moduli di test
I moduli di test sono moduli Android utilizzati solo a scopo di test.
I moduli contengono codice, risorse di test e dipendenze di test che sono
e non sono necessari durante il runtime dell'applicazione.
I moduli di test sono creati in modo da separare il codice specifico per il test dalla
rendendo il codice del modulo più facile da gestire e mantenere.
Casi d'uso per i moduli di test
I seguenti esempi illustrano situazioni in cui l'implementazione dei moduli di test
può essere particolarmente utile:
Codice di test condiviso. Se il progetto contiene più moduli e alcuni
il codice di test sia applicabile a più di un modulo, puoi creare un
per condividere il codice. In questo modo puoi ridurre i duplicati e rendere il tuo test
e il codice più semplice da gestire. Il codice di test condiviso può includere classi di utilità o
come asserzioni personalizzate o matcher, nonché dati di test come
di risposte JSON simulate.
Configurazioni build più chiare: i moduli di test ti consentono di avere a disposizione
configurazioni di build, in quanto possono avere un proprio file build.gradle. Non devi
non riempire il file build.gradle del modulo dell'app con configurazioni che sono
pertinente solo per i test.
Test di integrazione: è possibile utilizzare i moduli di test per archiviare l'integrazione
test utilizzati per verificare le interazioni tra le diverse parti della tua app
tra cui interfaccia utente, logica di business, richieste di rete e query del database.
Applicazioni su larga scala: i moduli di test sono particolarmente utili per
applicazioni su larga scala con codebase complessi e più moduli. In tale
casi, i moduli di test possono aiutare a migliorare l'organizzazione e la manutenibilità del codice.
di Gemini Advanced.
Figura 6. È possibile usare i moduli di test per isolare i moduli che altrimenti dipenderebbero l'uno dall'altro.
Comunicazione da modulo a modulo
I moduli raramente esistono in una separazione totale e spesso si basano su altri moduli e
comunicare con loro. È importante mantenere basso l'accoppiamento anche quando i moduli
collaborano e scambiano informazioni frequentemente. A volte diretta
la comunicazione tra due moduli non è auspicabile, come nel caso
vincoli dell'architettura. Potrebbe anche essere impossibile, come nel caso di modelli
delle dipendenze.
Figura 7. Una comunicazione diretta e bidirezionale tra i moduli è impossibile a causa di dipendenze cicliche. Un modulo di mediazione è necessario per coordinare il flusso di dati tra altri due moduli indipendenti.
Per risolvere questo problema puoi creare un terzo modulo di mediazione
tra altri due moduli. Il modulo mediatore può ascoltare i messaggi di entrambi
dei moduli e inoltrali se necessario. Nella nostra app di esempio, la procedura di pagamento
schermata deve sapere quale libro acquistare anche se l'evento ha avuto origine in
una schermata separata che fa parte di una funzionalità diversa. In questo caso,
il mediatore è il modulo proprietario del grafico di navigazione (di solito un modulo dell'app).
In questo esempio, utilizziamo la navigazione per trasmettere i dati dalla funzione Home alla
funzionalità di pagamento utilizzando il componente Navigazione.
navController.navigate("checkout/$bookId")
La destinazione di pagamento riceve un ID libro come argomento che utilizza per
recuperare informazioni sul libro. Puoi utilizzare l'handle dello stato salvato per
recuperare argomenti di navigazione all'interno di ViewModel di una funzionalità di destinazione.
classCheckoutViewModel(savedStateHandle:SavedStateHandle,…):ViewModel(){valuiState:StateFlow<CheckoutUiState>=savedStateHandle.getStateFlow<String>("bookId","").map{bookId->
// produce UI state calling bookRepository.getBook(bookId)}…}
Non devi passare oggetti come argomenti di navigazione. Usa invece ID semplici
utilizzabili per accedere e caricare le risorse desiderate dal livello dati.
In questo modo, mantieni basso l'accoppiamento e non violi la singola fonte di verità.
dell'IA.
Nell'esempio in basso, entrambi i moduli delle funzionalità dipendono dallo stesso modulo dati. Questo
consente di ridurre al minimo la quantità di dati di cui il modulo mediatore ha bisogno
in avanti e mantiene basso l'accoppiamento tra i moduli. Invece di passare
oggetti, i moduli dovrebbero scambiare ID primitivi e caricare le risorse da un
modulo dati condiviso.
Figura 8. Due moduli di funzionalità che si basano su un modulo di dati condiviso.
Inversione delle dipendenze
L'inversione delle dipendenze si verifica quando organizzi il codice in modo che l'astrazione sia
da un'implementazione concreta.
Astrazione: un contratto che definisce in che modo i componenti o i moduli nel tuo
le applicazioni interagiscono tra loro. I moduli di astrazione definiscono l'API
tuo sistema e contenere interfacce e modelli.
Implementazione concreta: moduli che dipendono dal modulo di astrazione
e implementare il comportamento di un'astrazione.
I moduli che si basano sul comportamento definito nel modulo di astrazione devono
dipendono dall'astrazione stessa, non dalle implementazioni specifiche.
Figura 9. Invece dei moduli di alto livello che dipendono direttamente dai moduli di basso livello, i moduli di alto livello e di implementazione dipendono dal modulo di astrazione.
Esempio
Immagina un modulo di funzionalità che necessita di un database per funzionare. Il modulo delle funzionalità non è
preoccupati di come viene implementato il database, che si tratti di un database di stanze
un'istanza Firestore remota. Deve solo archiviare e leggere i dati dell'applicazione.
Per ottenere questo risultato, il modulo delle funzionalità dipende dal modulo di astrazione anziché
rispetto a una specifica implementazione del database. Questa astrazione definisce
API di database. In altre parole, imposta le regole su come interagire con
per configurare un database. Ciò consente al modulo delle funzionalità di utilizzare qualsiasi database senza dover
conoscere i dettagli di implementazione di base.
Il modulo di implementazione concreto fornisce l'implementazione effettiva
API definite nel modulo di astrazione. A questo scopo, l'implementazione
dipende anche dal modulo di astrazione.
Inserimento delle dipendenze
A questo punto ti starai chiedendo in che modo il modulo delle funzionalità è collegato
nel modulo di implementazione. La risposta è Dipendency Injection. La funzionalità
non crea direttamente l'istanza di database richiesta. Invece,
e specifica le dipendenze di cui ha bisogno. Queste dipendenze vengono quindi fornite
all'esterno, di solito nel modulo dell'app.
I vantaggi di separare le API dalle loro implementazioni sono i seguenti:
Intercambiabilità: con una chiara separazione tra API e implementazione
puoi sviluppare più implementazioni per la stessa API e passare
senza modificare il codice che utilizza l'API. Potrebbe essere
particolarmente utile negli scenari in cui vuoi fornire
capacità o comportamenti in diversi contesti. Ad esempio, una simulazione
l'implementazione per i test rispetto a un'implementazione reale per la produzione.
Disaccoppiamento: la separazione significa che i moduli che utilizzano le astrazioni non
dipendono da una tecnologia specifica. Se scegli di modificare il database da
Spazio a Firestore in un secondo momento, sarebbe più semplice perché
si verificano nel modulo specifico in cui viene svolto il lavoro (modulo di implementazione) e non
influisce su altri moduli utilizzando l'API del database.
Testabilità: separare le API dalle loro implementazioni può notevolmente
per semplificare i test. Puoi scrivere scenari di test in base ai contratti API. Puoi
usare implementazioni diverse per testare vari scenari e casi limite,
incluse implementazioni fittizie.
Prestazioni di compilazione migliorate: quando separi un'API e i suoi
l'implementazione in moduli diversi, le modifiche all'implementazione
non costringono il sistema di compilazione a ricompilare i moduli, a seconda
API di Google Cloud. Questo si traduce in tempi di creazione più rapidi e
aumento della produttività,
in particolare nei progetti di grandi dimensioni, dove
i tempi di compilazione possono essere significativi.
Quando separare
È utile separare le API dalle loro implementazioni nel
i seguenti casi:
Funzionalità diverse: se puoi implementare parti del sistema in
in più modi, un'API chiara consente l'intercambiabilità
implementazioni. Ad esempio, potresti avere un sistema di rendering che utilizza OpenGL
o Vulkan oppure un sistema di fatturazione compatibile con Play o con la fatturazione interna
tramite Google Cloud CLI
o tramite l'API Compute Engine.
Applicazioni multiple: se stai sviluppando più applicazioni con
funzionalità condivise per diverse piattaforme, puoi definire API
sviluppare implementazioni specifiche per piattaforma.
Team indipendenti: la separazione consente a sviluppatori o team diversi di
lavorare contemporaneamente su diverse parti del codebase. Gli sviluppatori dovrebbero concentrarsi
sulla comprensione dei contratti API
e sul loro utilizzo corretto. Non è necessario
preoccuparsi dei dettagli di implementazione di altri moduli.
Codebase grande: quando il codebase è grande o complesso, l'API viene separata
dell'implementazione rende il codice più gestibile. Ti permette di interrompere
in unità più granulari, comprensibili e gestibili.
Come eseguire l'implementazione?
Per implementare l'inversione delle dipendenze, segui questi passaggi:
Crea un modulo di astrazione: questo modulo deve contenere API (interfacce
e modelli) che definisce il comportamento della caratteristica.
Crea moduli di implementazione. I moduli di implementazione devono basarsi sulle
modulo API e implementare il comportamento di un'astrazione.
Figura 10. I moduli di implementazione dipendono dal modulo di astrazione..
Rendi i moduli di alto livello dipendenti dai moduli di astrazione: invece di
a seconda di un'implementazione specifica, fai in modo che i moduli
moduli di astrazione. I moduli di alto livello non hanno bisogno di conoscere l'implementazione
dettagli, hanno bisogno solo del contratto (API).
Figura 11. I moduli di alto livello dipendono dalle astrazioni, non dall'implementazione..
Figura 12. Il modulo dell'app fornisce un'implementazione effettiva..
Best practice generali
Come accennato all'inizio, non esiste un solo modo giusto per sviluppare
un'app multimodulo. Proprio come esistono molte architetture software, esistono
molti modi per modularizzare un'app. Tuttavia, le seguenti informazioni generali
possono aiutarti a rendere il codice più leggibile, gestibile e
testabile.
Mantieni la configurazione coerente
Ogni modulo introduce l'overhead della configurazione. Se il numero di moduli
raggiunge una certa soglia, la gestione di una configurazione coerente diventa
sfida. Ad esempio, è importante che i moduli utilizzino dipendenze dello stesso
completamente gestita. Se devi aggiornare un numero elevato di moduli solo per spostare un
delle dipendenze, non è solo uno sforzo, ma anche uno spazio
e gli errori continui di configurazione. Per risolvere il problema, puoi usare uno degli strumenti di Gradle per
centralizza la configurazione:
I catalogi delle versioni sono un elenco sicuro dei tipi di dipendenze
generate da Gradle durante la sincronizzazione. È uno spazio centrale per dichiarare tutti i
ed è disponibile per tutti i moduli di un progetto.
L'interfaccia pubblica di un modulo dovrebbe essere minima ed esporre solo
di base. Non deve rendere pubblici dettagli dell'implementazione all'esterno. Ambito
tutto il più piccolo possibile. Usa private o internal di Kotlin
l'ambito di visibilità per rendere il modulo delle dichiarazioni private. Al momento della dichiarazione
dipendenze nel modulo, preferisci implementation rispetto a api. Quest'ultimo
espone dipendenze transitive ai consumer del modulo. Utilizzo
l'implementazione può migliorare i tempi di compilazione poiché riduce il numero di moduli
che devono essere ricostruiti.
Preferisco Kotlin e Moduli Java
Android Studio supporta tre tipi essenziali di moduli:
I moduli dell'app sono un punto di accesso alla tua applicazione. Possono contenere
codice sorgente, risorse, asset e un AndroidManifest.xml. L'output di un
Il modulo dell'app è un Android App Bundle (AAB) o un pacchetto di applicazioni Android
(APK).
I moduli della raccolta hanno gli stessi contenuti dei moduli dell'app. Sono
usato da altri moduli Android come dipendenza. L'output di un modulo della libreria
Android Archive (AAR) è strutturalmente identica ai moduli dell'app,
vengono compilate in un file AAR (Android Archive) che può essere successivamente utilizzato da altri
come dipendenza. Un modulo della libreria consente di
incapsulano e riutilizzano la stessa logica e le stesse risorse in molti moduli dell'app.
Le librerie Kotlin e Java non contengono risorse, asset o risorse Android o
manifest.
Poiché i moduli Android hanno un carico di lavoro, preferibilmente, è consigliabile utilizzare
Kotlin o Java il più possibile.
I campioni di contenuti e codice in questa pagina sono soggetti alle licenze descritte nella Licenza per i contenuti. Java e OpenJDK sono marchi o marchi registrati di Oracle e/o delle sue società consociate.
Ultimo aggiornamento 2025-07-27 UTC.
[[["Facile da capire","easyToUnderstand","thumb-up"],["Il problema è stato risolto","solvedMyProblem","thumb-up"],["Altra","otherUp","thumb-up"]],[["Mancano le informazioni di cui ho bisogno","missingTheInformationINeed","thumb-down"],["Troppo complicato/troppi passaggi","tooComplicatedTooManySteps","thumb-down"],["Obsoleti","outOfDate","thumb-down"],["Problema di traduzione","translationIssue","thumb-down"],["Problema relativo a esempi/codice","samplesCodeIssue","thumb-down"],["Altra","otherDown","thumb-down"]],["Ultimo aggiornamento 2025-07-27 UTC."],[],[]]