Depinde de abstracții
Principiul inversiunii dependențelor spune: codul de nivel înalt — logica ta de business — nu trebuie să depindă de detalii de nivel jos, precum o bază de date anume sau un format de fișier. Ambele trebuie să depindă de o abstracție: în TypeScript, o interfață.
„Inversiunea” este direcția săgeții. În loc ca serviciul să indice spre o singură stocare concretă, serviciul indică spre o interfață — iar fiecare stocare indică spre aceeași interfață, din cealaltă parte.
Legat direct de detalii
Acest serviciu de utilizatori creează și folosește direct un DiskStore concret. Funcționează — dar serviciul este sudat de o singură stocare. O altă stocare, una mai rapidă, una falsă pentru teste: fiecare dintre ele înseamnă modificarea serviciului însuși.
Serviciul sudat de stocarea sa
UserService își construiește singur DiskStore-ul în interior. Logica de înregistrare, de nivel înalt, depinde acum de un detaliu de nivel jos — fișierul de pe disc.
Ieșire: program TypeScript și rezultatul capturat
class DiskStore {
save(user: string): void {
console.log("se scrie", user, "în fișierul users.txt de pe disc")
}
}
// UserService își construiește singur stocarea — sudat de detaliu.
class UserService {
private store: DiskStore
constructor() {
this.store = new DiskStore()
}
register(user: string): void {
console.log("se înregistrează utilizatorul:", user)
this.store.save(user)
}
}
const service = new UserService()
service.register("ana")
Inversează dependența
Soluția: serviciul cere o interfață UserStorage în constructorul său și nu află niciodată pe care dintre ele a primit-o. Stocarea pe disc și cea în memorie implementează amândouă interfața.
Acum săgeata indică din ambele părți spre abstracție — iar schimbarea stocării este o modificare de o linie în locul unde este creat serviciul, nu în interiorul lui.
Serviciul acceptă o interfață
Aceeași logică de înregistrare, două stocări diferite. new UserService(new DiskStore()) sau new UserService(new MemoryStore()) — codul serviciului în sine nu se schimbă niciodată.
Ieșire: program TypeScript și rezultatul capturat
// Abstracția de care depind ambele părți.
interface UserStorage {
save(user: string): void
}
class DiskStore implements UserStorage {
save(user: string): void {
console.log("se scrie", user, "în fișierul users.txt de pe disc")
}
}
class MemoryStore implements UserStorage {
save(user: string): void {
console.log("se păstrează", user, "în memorie")
}
}
class UserService {
private store: UserStorage
constructor(store: UserStorage) {
this.store = store
}
register(user: string): void {
console.log("se înregistrează utilizatorul:", user)
this.store.save(user)
}
}
const onDisk = new UserService(new DiskStore())
onDisk.register("ana")
const inMemory = new UserService(new MemoryStore())
inMemory.register("ben")
Câștigul: testare fără disc
Pentru că serviciul depinde de abstracție, un test îi poate da o stocare în memorie și poate verifica rezultatul instantaneu — fără fișiere, fără curățenie. Inversiunea dependențelor este cea care face codul ușor de testat unitar.
Ieșire: program TypeScript și rezultatul capturat
interface UserStorage {
save(user: string): void
}
// MemoryStore reține ce a fost salvat — perfect pentru verificări.
class MemoryStore implements UserStorage {
users: string[] = []
save(user: string): void {
this.users.push(user)
}
}
class UserService {
private store: UserStorage
constructor(store: UserStorage) {
this.store = store
}
register(user: string): void {
this.store.save(user)
}
}
const store = new MemoryStore()
const service = new UserService(store)
service.register("ana")
service.register("ben")
if (store.users.length === 2) {
console.log("verificare trecută: 2 utilizatori salvați")
}
console.log("utilizatori salvați:", store.users.join(", "))