Depinde de abstracții
Principiul inversiunii dependențelor spune: codul de nivel înalt — logica ta de business — nu ar trebui să depindă de detalii de nivel jos, cum ar fi o anumită bază de date sau un anumit format de fișier. Amândouă ar trebui să depindă de o abstracție: în Go, o interfață.
„Inversiunea” este direcția săgeții. În loc ca serviciul să arate spre o stocare concretă, serviciul arată spre o interfață — iar fiecare stocare arată spre aceeași interfață, din cealaltă parte.
Legat rigid 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 lui
UserService își construiește propriul DiskStore înăuntru. Logica de înregistrare, de nivel înalt, depinde acum de un detaliu de nivel jos — fișierul de pe disc.
Ieșire: program Go și rezultatul capturat
package main
import "fmt"
type DiskStore struct{}
func (d DiskStore) Save(user string) {
fmt.Println("se scrie", user, "în fișierul users.txt pe disc")
}
// UserService își construiește singur stocarea — sudat de detaliu.
type UserService struct {
store DiskStore
}
func (s UserService) Register(user string) {
fmt.Println("se înregistrează utilizatorul:", user)
s.store.Save(user)
}
func main() {
service := UserService{store: DiskStore{}}
service.Register("ana")
}
Inversează dependența
Soluția: serviciul cere o interfață Storage în constructorul lui și nu află niciodată pe care dintre stocări a primit-o. Stocarea pe disc și stocarea în memorie implementează amândouă interfața.
Acum săgeata arată dinspre 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. NewUserService(DiskStore{}) sau NewUserService(MemoryStore{}) — codul serviciului nu se schimbă deloc.
Ieșire: program Go și rezultatul capturat
package main
import "fmt"
// Abstracția de care depind ambele părți.
type Storage interface {
Save(user string)
}
type DiskStore struct{}
func (d DiskStore) Save(user string) {
fmt.Println("se scrie", user, "în fișierul users.txt pe disc")
}
type MemoryStore struct{}
func (m MemoryStore) Save(user string) {
fmt.Println("se păstrează", user, "în memorie")
}
type UserService struct {
store Storage
}
func NewUserService(store Storage) UserService {
return UserService{store: store}
}
func (s UserService) Register(user string) {
fmt.Println("se înregistrează utilizatorul:", user)
s.store.Save(user)
}
func main() {
onDisk := NewUserService(DiskStore{})
onDisk.Register("ana")
inMemory := NewUserService(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 după. Inversiunea dependențelor este cea care face codul ușor de testat unitar.
Ieșire: program Go și rezultatul capturat
package main
import "fmt"
type Storage interface {
Save(user string)
}
// MemoryStore reține ce a fost salvat — perfect pentru verificări.
type MemoryStore struct {
users []string
}
func (m *MemoryStore) Save(user string) {
m.users = append(m.users, user)
}
type UserService struct {
store Storage
}
func NewUserService(store Storage) UserService {
return UserService{store: store}
}
func (s UserService) Register(user string) {
s.store.Save(user)
}
func main() {
store := &MemoryStore{}
service := NewUserService(store)
service.Register("ana")
service.Register("ben")
if len(store.users) == 2 {
fmt.Println("verificare reușită: 2 utilizatori salvați")
}
fmt.Println("utilizatori salvați:", store.users)
}