Injeksi dependensi (DI) adalah teknik yang banyak digunakan dalam pemrograman dan sesuai dengan pengembangan Android. Dengan mengikuti prinsip-prinsip DI, Anda mengatur dasar arsitektur aplikasi yang baik.
Implementasi injeksi dependensi memberikan beberapa keuntungan berikut:
- Penggunaan kembali kode
- Kemudahan dalam pemfaktoran ulang
- Kemudahan dalam pengujian
Dasar-dasar injeksi dependensi
Sebelum membahas injeksi dependensi pada Android secara khusus, halaman ini memberikan ringkasan umum tentang cara kerja injeksi dependensi.
Apa itu injeksi dependensi?
Class sering kali memerlukan referensi ke class lain. Misalnya, class Car
mungkin memerlukan referensi ke class Engine
. Class wajib ini disebut
dependensi, dan dalam contoh ini class Car
bergantung pada
sebuah instance dari class Engine
untuk dijalankan.
Ada tiga cara bagi suatu class untuk mendapatkan objek yang dibutuhkan:
- Class menyusun dependensi yang dibutuhkan. Pada contoh di atas,
Car
akan membuat dan melakukan inisialisasi instanceEngine
-nya sendiri. - Ambil dari tempat lain. Beberapa API Android, seperti
pengambil
Context
dangetSystemService()
, berfungsi seperti ini. - Masukkan sebagai parameter. Aplikasi dapat menyediakan
dependensi ini saat class dibuat atau meneruskannya ke fungsi
yang memerlukan setiap dependensi. Pada contoh di atas, konstruktor
Car
akan menerimaEngine
sebagai parameter.
Opsi ketiga adalah injeksi dependensi! Dengan pendekatan ini, Anda mengambil dependensi class dan menyediakannya, bukan meminta instance class untuk mendapatkannya sendiri.
Berikut contohnya. Tanpa injeksi dependensi, representasi Car
yang
membuat dependensi Engine
-nya sendiri dalam kode akan terlihat seperti ini:
Kotlin
class Car { private val engine = Engine() fun start() { engine.start() } } fun main(args: Array) { val car = Car() car.start() }
Java
class Car { private Engine engine = new Engine(); public void start() { engine.start(); } } class MyApp { public static void main(String[] args) { Car car = new Car(); car.start(); } }
Ini bukan contoh injeksi dependensi karena class Car
menyusun Engine
-nya sendiri. Hal ini dapat menjadi masalah karena:
Car
danEngine
disandingkan dengan erat - instanceCar
menggunakan satu jenisEngine
, dan tidak ada subclass atau implementasi alternatif yang dapat digunakan dengan mudah. JikaCar
menyusunEngine
-nya sendiri, Anda harus membuat dua jenisCar
, bukan hanya menggunakan kembaliCar
yang sama untuk mesin berjenisGas
danElectric
.Dependensi keras pada
Engine
membuat pengujian menjadi lebih sulit.Car
menggunakan instance nyataEngine
, sehingga Anda tidak perlu menggunakan pengujian ganda untuk mengubahEngine
untuk kasus pengujian yang berbeda.
Seperti apa tampilan kode dengan injeksi dependensi? Setiap instance
Car
tidak menyusun objek Engine
-nya sendiri pada saat inisialisasi, tetapi justru menerima
objek Engine
sebagai parameter dalam konstruktornya:
Kotlin
class Car(private val engine: Engine) { fun start() { engine.start() } } fun main(args: Array) { val engine = Engine() val car = Car(engine) car.start() }
Java
class Car { private final Engine engine; public Car(Engine engine) { this.engine = engine; } public void start() { engine.start(); } } class MyApp { public static void main(String[] args) { Engine engine = new Engine(); Car car = new Car(engine); car.start(); } }
Fungsi main
menggunakan Car
. Karena Car
bergantung pada Engine
, aplikasi akan membuat
instance Engine
, kemudian menggunakannya untuk membuat instance Car
. Manfaat
pendekatan berbasis DI ini adalah:
Penggunaan kembali
Car
. Anda dapat meneruskan berbagai implementasiEngine
keCar
. Misalnya, Anda dapat menentukan subclassEngine
baru yang disebutElectricEngine
dan Anda inginkan agar digunakan olehCar
. Jika menggunakan DI, Anda hanya perlu meneruskan instance subclassElectricEngine
yang diperbarui, danCar
akan tetap berfungsi tanpa adanya perubahan lebih lanjut.Kemudahan pengujian
Car
. Anda dapat meneruskan pengujian ganda untuk menguji skenario yang berbeda. Misalnya, Anda dapat membuat pengujian gandaEngine
yang disebutFakeEngine
dan mengonfigurasinya untuk pengujian yang lain.
Ada dua cara utama untuk melakukan injeksi dependensi pada Android:
Injeksi Konstruktor. Ini adalah cara yang dideskripsikan di atas. Anda meneruskan dependensi class ke konstruktornya.
Injeksi Kolom (atau Injeksi Penyetel). Class framework Android tertentu, seperti aktivitas dan fragmen, dibuat instance-nya oleh sistem, sehingga injeksi konstruktor tidak dapat dilakukan. Dengan injeksi kolom, dependensi akan dibuat instance-nya setelah class dibuat. Kodenya akan terlihat seperti ini:
Kotlin
class Car { lateinit var engine: Engine fun start() { engine.start() } } fun main(args: Array) { val car = Car() car.engine = Engine() car.start() }
Java
class Car { private Engine engine; public void setEngine(Engine engine) { this.engine = engine; } public void start() { engine.start(); } } class MyApp { public static void main(String[] args) { Car car = new Car(); car.setEngine(new Engine()); car.start(); } }
Injeksi dependensi otomatis
Pada contoh sebelumnya, Anda membuat, menyediakan, dan mengelola sendiri dependensi
berbagai class, tanpa mengandalkan library. Ini disebut
injeksi dependensi tanpa bantuan alat, atau injeksi dependensi manual. Dalam
contoh Car
, hanya ada satu dependensi. Namun, lebih banyak dependensi dan class dapat membuat injeksi dependensi manual menjadi lebih melelahkan. Injeksi dependensi manual
juga mendatangkan beberapa masalah:
Untuk aplikasi besar, mengambil semua dependensi dan menghubungkannya dengan benar mungkin akan memerlukan jumlah kode boilerplate yang besar. Dalam arsitektur multi-lapisan, untuk membuat sebuah objek pada lapisan atas, Anda harus menyediakan semua dependensi lapisan di bawahnya. Sebagai contoh konkret, untuk membuat mobil sungguhan, Anda mungkin memerlukan mesin, transmisi, rangka, dan komponen lainnya; dan pada gilirannya, mesin memerlukan silinder dan busi.
Saat Anda tidak dapat membuat dependensi sebelum meneruskannya — misalnya ketika menggunakan inisialisasi yang lambat atau mencakup objek ke alur aplikasi — Anda harus menulis dan mempertahankan container kustom (atau grafik dependensi) yang mengelola masa aktif dependensi dalam memori.
Ada library yang dapat memecahkan masalah ini dengan mengotomatiskan proses pembuatan dan penyediaan dependensi. Kedua solusi tersebut sesuai dengan dua kategori:
Solusi berbasis refleksi yang menghubungkan dependensi pada runtime.
Solusi statis yang menghasilkan kode untuk menghubungkan dependensi pada waktu kompilasi.
Dagger adalah library injeksi dependensi yang populer untuk Java, Kotlin, dan Android yang dikelola oleh Google. Dagger memfasilitasi penggunaan DI pada aplikasi Anda dengan membuat dan mengelola grafik dependensi untuk Anda. Dagger menyediakan dependensi statis sepenuhnya dan waktu kompilasi yang mengatasi berbagai masalah performa dan pengembangan dengan solusi berbasis refleksi seperti Guice.
Alternatif untuk injeksi dependensi
Alternatif untuk injeksi dependensi adalah dengan menggunakan pencari lokasi layanan. Pola desain pencari lokasi layanan juga meningkatkan pemisahan class dari dependensi konkret. Anda membuat class yang dikenal sebagai pencari lokasi layanan yang membuat dan menyimpan dependensi, lalu menyediakan dependensi itu sesuai permintaan.
Kotlin
object ServiceLocator { fun getEngine(): Engine = Engine() } class Car { private val engine = ServiceLocator.getEngine() fun start() { engine.start() } } fun main(args: Array) { val car = Car() car.start() }
Java
class ServiceLocator { private static ServiceLocator instance = null; private ServiceLocator() {} public static ServiceLocator getInstance() { if (instance == null) { synchronized(ServiceLocator.class) { instance = new ServiceLocator(); } } return instance; } public Engine getEngine() { return new Engine(); } } class Car { private Engine engine = ServiceLocator.getInstance().getEngine(); public void start() { engine.start(); } } class MyApp { public static void main(String[] args) { Car car = new Car(); car.start(); } }
Pola pencari lokasi layanan berbeda dengan injeksi dependensi dalam cara elemen tersebut digunakan. Dengan pola pencari lokasi layanan, class memiliki kontrol dan meminta agar objek diinjeksikan; dengan injeksi dependensi, aplikasi memiliki kontrol dan menginjeksikan objek yang diperlukan secara proaktif.
Dibandingkan dengan injeksi dependensi:
Pengumpulan dependensi yang diperlukan oleh pencari lokasi layanan membuat kode lebih sulit diuji karena semua pengujian harus berinteraksi dengan pencari lokasi layanan global yang sama.
Dependensi dienkode dalam implementasi class, bukan pada permukaan API. Akibatnya, lebih sulit untuk mengetahui apa yang dibutuhkan oleh class dari luar. Akibatnya, perubahan pada
Car
atau dependensi yang tersedia pada pencari lokasi layanan dapat menyebabkan kegagalan pengujian atau runtime dengan menyebabkan referensi gagal.Mengelola masa aktif objek akan lebih sulit jika Anda ingin mencakup apa pun selain masa aktif seluruh aplikasi.
Menggunakan Hilt di aplikasi Android Anda
Hilt adalah library yang direkomendasikan Jetpack untuk injeksi dependensi di Android. Hilt mendefinisikan cara standar untuk melakukan DI dalam aplikasi Anda dengan menyediakan container untuk setiap class Android dalam project Anda dan mengelola siklus prosesnya secara otomatis untuk Anda.
Hilt ditambahkan pada Dagger library DI yang populer untuk mendapatkan manfaat dari ketepatan waktu kompilasi, performa runtime, skalabilitas, dan dukungan Android Studio yang disediakan oleh Dagger.
Untuk mempelajari Hilt lebih lanjut, lihat Injeksi Dependensi dengan Hilt.
Kesimpulan
Injeksi dependensi memberi aplikasi Anda keuntungan sebagai berikut:
Penggunaan kembali class dan pemisahan dependensi: Lebih mudah untuk menukar implementasi dependensi. Penggunaan kembali kode ditingkatkan karena inversi kontrol, dan class tidak lagi mengontrol cara dependensi dibuat, tetapi berfungsi dengan konfigurasi apa pun.
Kemudahan pemfaktoran ulang: Dependensi menjadi bagian permukaan API yang dapat diverifikasi, sehingga dapat diperiksa pada waktu pembuatan objek atau pada waktu kompilasi, bukan disembunyikan sebagai detail implementasi.
Kemudahan pengujian: Class tidak mengelola dependensinya, sehingga saat sedang mengujinya, Anda dapat meneruskan berbagai implementasi untuk menguji semua kasus yang berbeda.
Untuk memahami sepenuhnya manfaat injeksi dependensi, Anda harus mencobanya secara manual di aplikasi Anda seperti yang ditunjukkan pada Injeksi dependensi manual.
Referensi lainnya
Untuk mempelajari injeksi dependensi lebih lanjut, lihat referensi tambahan berikut.