โ Kembali ke Konsep OOP
๐
Prinsip
Dependency Inversion
Module tingkat tinggi tidak boleh bergantung langsung pada module tingkat rendah; keduanya harus bergantung pada abstraksi.
Masalahnya
Saat business logic tingkat tinggi langsung membuat dan bergantung pada class konkret tingkat rendah (misalnya email client tertentu), kodenya jadi kaku, sulit dites, dan terikat erat pada detail implementasi yang bisa berubah.
Cara Kerja
- โบModule tingkat tinggi dan rendah sama-sama bergantung pada abstraksi bersama (interface/protocol)
- โบImplementasi konkret "di-inject" ke module tingkat tinggi lewat constructor atau property injection
- โบMemungkinkan pertukaran implementasi (misalnya sender asli vs. mock) tanpa mengubah business logic
- โบMenjadi dasar framework dependency injection serta arsitektur berlapis yang mudah dites
Kapan Digunakan
โBusiness logic perlu bisa dites dengan dependency yang di-mock
โMengganti infrastruktur (database, network client, file system) tanpa menyentuh logika domain
โMemisahkan lapisan dalam clean architecture atau hexagonal architecture
Contoh di Dunia Nyata
๐
iOS
Meng-inject NetworkServiceProtocol ke ViewModel alih-alih memakai URLSession secara langsung
๐ค
Android
Hilt/Dagger meng-inject implementasi interface Repository ke dalam ViewModel
๐
Web
Mengoper interface database ke constructor service, yang bisa ditukar dengan implementasi in-memory saat testing
Contoh Kode
Swift
protocol MessageSender {
func send(_ message: String)
}
struct EmailSender: MessageSender {
func send(_ message: String) { print("Email: \(message)") }
}
struct SMSSender: MessageSender {
func send(_ message: String) { print("SMS: \(message)") }
}
// High-level module depends on the abstraction, not a concrete sender
struct NotificationService {
let sender: MessageSender
func notify(_ message: String) {
sender.send(message)
}
}
let service = NotificationService(sender: SMSSender())
service.notify("Your order has shipped")