DIP: fondamenti, inversione delle dipendenze nello sviluppo

Autore: IT Sectr Pubblicato: 2026-05-12 Tempo di lettura: 9 min

DIP (Dependency Inversion Principle) — il quinto principio SOLID che definisce le regole per costruire dipendenze tra moduli: i moduli di alto livello non devono dipendere da moduli di basso livello, entrambi devono dipendere da astrazioni. Le astrazioni non devono dipendere dai dettagli — i dettagli devono dipendere dalle astrazioni. Questo principio, descritto da Robert Martin in Clean Architecture (2017), è alla base dell'architettura a basso accoppiamento. Secondo questo libro, il principio di inversione delle dipendenze elimina gli accoppiamenti rigidi tra i livelli dell'applicazione.

Punti chiave

  • DIP — principio di inversione delle dipendenze, il quinto di SOLID, sui confini architetturali
  • I moduli di alto livello non devono importare moduli di basso livello — solo astrazioni
  • DIP ≠ DI: Dependency Inversion è un principio architetturale, Dependency Injection è un modo per implementarlo
  • Le astrazioni appartengono al modulo di alto livello, le implementazioni a quello di basso livello
  • DIP inverte la gerarchia tradizionale delle dipendenze nelle architetture multistrato

Cos'è DIP (Dependency Inversion Principle)?

DIP (Dependency Inversion Principle) è il principio di inversione delle dipendenze che inverte la visione tradizionale sulla direzione delle dipendenze tra moduli. I moduli di alto livello (logica di business) non devono dipendere direttamente da moduli di basso livello (database, rete, UI). Invece, entrambi i livelli dipendono da astrazioni definite nel modulo di alto livello.

La formulazione formale del DIP include due regole: A — i moduli di alto livello non devono dipendere da moduli di basso livello, entrambi devono dipendere da astrazioni. B — le astrazioni non devono dipendere dai dettagli, i dettagli devono dipendere dalle astrazioni. La seconda regola deriva dalla prima: se un'astrazione dipende dai dettagli, non può essere una base stabile per un modulo di alto livello.

Senza DIP, un'architettura tipica si presenta così: BusinessLogic → DatabaseRepository — la logica di business dipende direttamente da un repository concreto. Con DIP: BusinessLogic → DatabaseServiceInterface ← DatabaseRepository. BusinessLogic non conosce l'esistenza di DatabaseRepository, conosce solo l'interfaccia DatabaseService, che viene implementata al di fuori della logica di business.

Direzione delle dipendenze in DIP

Inversione significa che il flusso di controllo e il flusso delle dipendenze vanno in direzioni opposte. Il flusso di controllo va dall'alto verso il basso: UI → ViewModel → UseCase → Repository. Il flusso delle dipendenze va dal basso verso l'alto: Repository implementa un'interfaccia definita in UseCase. Repository (basso livello) dipende da UseCase (alto livello).

Questa inversione è la differenza chiave tra DIP e la separazione ordinaria in livelli. Nell'architettura tradizionale a livelli, ogni livello dipende dal livello sottostante. Nell'architettura con DIP, tutti i livelli dipendono da astrazioni, mentre l'implementazione di queste astrazioni risiede nel livello di infrastruttura, che viene “collegato” ai livelli superiori tramite meccanismi DI.

Come funziona il principio di inversione delle dipendenze

Il meccanismo DIP viene implementato definendo astrazioni nei moduli di alto livello e realizzandole nei moduli di basso livello. Il modulo di alto livello dichiara un'interfaccia per la funzionalità di cui ha bisogno. Il modulo di basso livello implementa questa interfaccia. Il cablaggio (wiring) avviene nella radice di composizione dell'applicazione.

Il processo di introduzione del DIP in codice esistente: estrarre un'interfaccia per il modulo di basso livello, spostare questa interfaccia nel modulo di alto livello (o in un livello di astrazione separato), riscrivere la dipendenza del modulo di alto livello per utilizzare l'interfaccia, far implementare questa interfaccia al modulo di basso livello. Dopo questi passaggi, la direzione della dipendenza è stata invertita.

DIP richiede un meccanismo di radice di composizione — un punto nell'applicazione dove tutte le dipendenze vengono create e collegate tra loro. In Android è Application.get() o il componente Hilt, in iOS — AppDelegate o SceneDelegate. La radice di composizione è l'unico posto dove il codice conosce le implementazioni concrete.

Isolamento dei livelli tramite DIP

DIP crea confini architetturali tra i livelli dell'applicazione. Quando ViewModel dipende dall'interfaccia UserRepository, si forma un confine tra il livello di presentazione e quello di dominio: ViewModel (presentazione) non sa da dove provengono i dati. Questo confine permette di cambiare l'implementazione di UserRepository (Room → REST → Mock) senza influenzare ViewModel. Più confini di questo tipo, più l'applicazione è resiliente ai cambiamenti di framework e librerie.

Nell'architettura Android raccomandata da Google, DIP viene implementato tramite UseCase che risiedono nel livello di dominio e dipendono da interfacce Repository. RepositoryImpl si trovano nel livello dati e implementano queste interfacce. Il livello di presentazione (ViewModel) dipende da UseCase. La direzione delle dipendenze va dalla presentazione al dominio, dal dominio ai dati — ma nessun livello conosce le implementazioni concrete di un altro livello.

Differenza tra DIP e DI (Dependency Injection)

DIP e DI sono spesso confusi, ma sono concetti diversi. DIP è un principio architetturale (COSA fare: dipendere da astrazioni). DI è un pattern implementativo (COME farlo: passare dipendenze tramite costruttore). DIP risponde alla domanda “su cosa devono basarsi i moduli?”, DI risponde a “come gli oggetti ottengono le loro dipendenze?”.

Dependency Injection è un modo per iniettare dipendenze in un oggetto tramite costruttore, metodo o proprietà. Quando una classe Kotlin riceve un'interfaccia Repository tramite il suo costruttore — questo è DI. Il fatto che una classe ViewModel dipenda dall'interfaccia Repository e non da un'implementazione concreta di RoomRepository — questo è DIP. DI è lo strumento, DIP è l'obiettivo.

Si può seguire DIP senza un framework DI: il cablaggio manuale delle dipendenze nella radice di composizione è anch'esso DI (DI manuale). Si può usare un framework DI (Dagger, Hilt, Koin) violando DIP: se ViewModel crea direttamente un oggetto Repository tramite new() — DIP è violato, anche se il framework è installato. DIP è una decisione architetturale, DI è un dettaglio tecnico.

Esempi di DIP nello sviluppo mobile

Consideriamo un esempio Android di applicazione del DIP al livello dati. Senza DIP, un ViewModel crea direttamente una RoomDatabase e DAO. Con DIP — il ViewModel dipende dall'interfaccia UserRepository, e l'implementazione concreta RoomUserRepository viene fornita esternamente.

kotlin
// L'astrazione appartiene al livello di dominio (alto livello)
interface UserRepository {
    fun getUser(id: Int): User
}

// Il livello di dominio dipende solo dall'astrazione
class GetUserUseCase(
    private val repo: UserRepository
) {
    fun execute(id: Int): User = repo.getUser(id)
}

// L'implementazione nel livello dati dipende dall'astrazione del livello di dominio
class RoomUserRepository(
    private val dao: UserDao
) : UserRepository {
    override fun getUser(id: Int): User {
        return dao.getById(id)
    }
}

// Radice di composizione
class AppModule {
    fun provideUserRepository(dao: UserDao): UserRepository {
        return RoomUserRepository(dao)
    }
}

Un esempio iOS con un Application Coordinator e un protocollo di navigazione:

swift
// Astrazione di navigazione nel livello di dominio
protocol AuthNavigation {
    func navigateToHome()
    func navigateToLogin()
}

// ViewModel dipende dall'astrazione, non da UIKit
final class AuthViewModel {
    private let navigation: AuthNavigation

    init(navigation: AuthNavigation) {
        self.navigation = navigation
    }

    func onLoginSuccess() {
        navigation.navigateToHome()
    }
}

// Coordinator (livello UIKit) implementa il protocollo del livello di dominio
final class AppCoordinator: AuthNavigation {
    func navigateToHome() {
        // Codice di navigazione UIKit
    }
    func navigateToLogin() {
        // Codice di navigazione UIKit
    }
}

Il punto chiave: AuthViewModel (dominio) non conosce l'esistenza di AppCoordinator (UIKit). Conosce solo il protocollo AuthNavigation. Se UIKit venisse sostituito da SwiftUI domani — AuthViewModel non richiede modifiche. DIP rende il livello di dominio indipendente dai framework e dalle librerie UI.

Strumenti per DIP: Dagger, Hilt, Koin

Hilt è lo strumento DI standard per Android, raccomandato da Google. È integrato in Jetpack, supporta ViewModel, Fragment, Service e altri componenti Android. Hilt automatizza la creazione della radice di composizione tramite le annotazioni @Module, @Provides, @Inject. Usare Hilt non garantisce il rispetto del DIP — l'interfaccia UserRepository deve essere definita nel livello di dominio, non nel livello dati.

Koin è un framework DI leggero per Kotlin senza generazione di codice o elaborazione di annotazioni. Il DSL di Koin (module, single, factory) è più facile da imparare, ma la verifica delle dipendenze avviene a runtime anziché in fase di compilazione. Koin è popolare nei progetti multipiattaforma (KMP) grazie al supporto iOS.

Dagger 2 è il predecessore di Hilt, ancora utilizzato in grandi progetti. Dagger genera codice DI in fase di compilazione, offrendo massime prestazioni e diagnosi degli errori in fase di build. Hilt è costruito su Dagger e fornisce un'API semplificata. Per i nuovi progetti, Google raccomanda Hilt come framework DI principale.

Organizzazione dei moduli DI per livelli

I moduli DI devono corrispondere ai livelli architetturali ed essere separati in DomainModule, DataModule, PresentationModule. DomainModule fornisce solo astrazioni e UseCase. DataModule fornisce implementazioni per le astrazioni. PresentationModule collega ViewModel a UseCase. Questa organizzazione garantisce che il livello di dominio rimanga indipendente dalle librerie di infrastruttura.

Durante la migrazione tra framework DI (ad esempio, da Koin a Hilt), la struttura di DomainModule non cambia — cambiano solo i metodi di cablaggio in DataModule e PresentationModule. DIP garantisce l'isolamento della logica di dominio, mentre il framework DI è un meccanismo tecnico di cablaggio.

Domande frequenti

Bisogna applicare sempre DIP?

DIP è necessario ai confini architetturali — tra i livelli dell'applicazione (dominio → dati, presentazione → dominio). All'interno di un singolo livello, DIP può essere eccessivo. Ad esempio, una classe utility StringFormatter all'interno del livello di dominio non richiede un'interfaccia — se non c'è motivo di sostituirla.

DIP è la stessa cosa dell'iniezione delle dipendenze?

No. DIP è un principio: i moduli devono dipendere da astrazioni. DI è un pattern: un oggetto riceve dipendenze dall'esterno anziché crearle da sé. DI è un modo per implementare DIP, ma DIP può essere seguito senza DI (tramite fabbriche o service locator). DI senza DIP è possibile ma non ha valore architetturale.

Dove definire le interfacce per DIP?

Le interfacce appartengono al modulo che le usa, non al modulo che le implementa. UserRepository viene dichiarato nel livello di dominio e implementato nel livello dati. Questa è la regola chiave di DIP: il proprietario dell'astrazione è il consumatore, non il fornitore dell'implementazione.

Come influisce DIP sui test?

DIP rende possibile il test su livelli isolati. Un ViewModel che dipende da UserRepository (interfaccia) può essere testato con un'implementazione mock senza database. Senza DIP, il ViewModel dipenderebbe da RoomUserRepository e richiederebbe la configurazione del database per ogni test. DIP + DI forniscono un completo isolamento dei moduli durante i test.

Quale framework DI scegliere per Android?

Hilt è la scelta standard per i progetti Android, raccomandata da Google. Koin è un'alternativa per i progetti Kotlin Multiplatform. Dagger 2 è per progetti esistenti dove la migrazione a Hilt non è giustificata. La scelta del framework non elimina la necessità di seguire DIP a livello architetturale.

Riepilogo

  • DIP (Dependency Inversion Principle) — il quinto principio SOLID sui confini architetturali attraverso le astrazioni
  • I moduli di alto livello non dipendono da moduli di basso livello — entrambi dipendono da astrazioni
  • DIP ≠ DI: principio vs pattern implementativo; DI è lo strumento, DIP è l'obiettivo
  • Le astrazioni appartengono al consumatore (livello di dominio), non al fornitore (livello dati)
  • Radice di composizione — l'unico posto nell'applicazione dove le dipendenze concrete vengono assemblate
  • Hilt, Koin, Dagger — strumenti DI che automatizzano il cablaggio ma non sostituiscono la decisione architetturale del DIP
  • Il livello di dominio costruito secondo DIP rimane indipendente da framework, UI e librerie di infrastruttura

Svilupperemo un'applicazione mobile chiavi in mano

IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.

Discuti il progetto

Leggi anche