Dasar-dasar Injeksi Ketergantungan

Dasar-dasar Injeksi Ketergantungan


Dalam artikel ini, saya akan berbicara tentang dasar-dasar injeksi ketergantungan (Eng. Dependency Injection, DI ) dalam bahasa yang sederhana, dan juga tentang alasan untuk menggunakan pendekatan ini. Artikel ini ditujukan bagi mereka yang tidak tahu injeksi ketergantungan apa, atau yang meragukan perlunya menggunakan teknik ini. Jadi mari kita mulai.


Apa itu kecanduan?


Mari kita lihat contoh pertama. Kami memiliki ClassA , ClassB dan ClassC seperti yang ditunjukkan di bawah ini:


 class ClassA { var classB: ClassB } class ClassB { var classC: ClassC } class ClassC { } 

Anda dapat melihat bahwa kelas ClassA berisi turunan dari kelas ClassB , jadi kita dapat mengatakan bahwa kelas ClassA bergantung pada kelas ClassB . Mengapa Karena ClassA membutuhkan ClassB untuk bekerja dengan benar. Kita juga dapat mengatakan bahwa kelas ClassB adalah ketergantungan dari kelas ClassA .


Sebelum melanjutkan, saya ingin menjelaskan bahwa hubungan seperti itu baik, karena kita tidak perlu satu kelas untuk melakukan semua pekerjaan dalam aplikasi. Kita perlu membagi logika ke dalam kelas yang berbeda, yang masing-masing akan bertanggung jawab untuk fungsi tertentu. Dan dalam hal ini, kelas-kelas akan dapat berinteraksi secara efektif.


Bagaimana cara bekerja dengan dependensi?


Mari kita lihat tiga metode yang digunakan untuk melakukan tugas injeksi ketergantungan:


Cara pertama: buat dependensi di kelas dependen


Sederhananya, kita dapat membuat objek kapan pun kita membutuhkannya. Lihatlah contoh berikut:


 class ClassA { var classB: ClassB fun someMethodOrConstructor() { classB = ClassB() classB.doSomething() } } 

Sangat mudah! Kami membuat kelas saat kami membutuhkannya.


Manfaatnya


  • Mudah dan sederhana.
  • Kelas dependen ( ClassA dalam kasus kami) sepenuhnya mengontrol bagaimana dan kapan membuat dependensi.

Kekurangan


  • ClassA dan ClassB terkait erat satu sama lain. Karena itu, setiap kali kita perlu menggunakan ClassA , kita akan dipaksa untuk menggunakan ClassB , dan tidak mungkin untuk mengganti ClassB dengan yang lain .
  • Dengan perubahan apa pun dalam inisialisasi kelas ClassB , Anda harus menyesuaikan kode di dalam kelas ClassA (dan semua kelas lainnya bergantung pada ClassB ). Ini menyulitkan proses mengubah ketergantungan.
  • ClassA tidak dapat diuji. Jika Anda perlu menguji suatu kelas, namun ini adalah salah satu aspek terpenting dari pengembangan perangkat lunak, maka Anda harus melakukan pengujian unit masing-masing kelas secara terpisah. Ini berarti bahwa jika Anda ingin memverifikasi operasi yang benar dari kelas ClassA dan membuat beberapa tes unit untuk memverifikasinya, maka, seperti yang ditunjukkan dalam contoh, Anda akan membuat instance kelas ClassB , bahkan ketika Anda tidak tertarik dengannya. Jika kesalahan terjadi selama pengujian, maka Anda tidak akan dapat memahami di mana letaknya - di ClassA atau ClassB . Bagaimanapun, ada kemungkinan bahwa bagian dari kode di ClassB menyebabkan kesalahan, sementara ClassA bekerja dengan benar. Dengan kata lain, pengujian unit tidak dimungkinkan karena modul (kelas) tidak dapat dipisahkan satu sama lain.
  • ClassA harus dikonfigurasi sehingga dapat menyuntikkan dependensi. Dalam contoh kita, dia perlu tahu cara membuat ClassC dan menggunakannya untuk membuat ClassB . Akan lebih baik jika dia tidak tahu apa-apa tentang itu. Mengapa Karena prinsip tanggung jawab tunggal .

Setiap kelas seharusnya hanya melakukan tugasnya.

Oleh karena itu, kami tidak ingin kelas bertanggung jawab atas apa pun selain tugas mereka sendiri. Implementasi dependensi adalah tugas tambahan yang kami tetapkan untuk mereka.


Cara kedua: menyuntikkan dependensi melalui kelas kustom


Jadi, memahami bahwa menyuntikkan dependensi dalam kelas dependen bukanlah ide yang baik, mari kita jelajahi cara alternatif. Di sini, kelas dependen mendefinisikan semua dependensi yang dibutuhkan di dalam konstruktor dan memungkinkan kelas pengguna untuk menyediakannya. Apakah ini solusi untuk masalah kita? Kami akan mencari tahu nanti.


Lihatlah kode contoh di bawah ini:


 class ClassA { var classB: ClassB constructor(classB: ClassB){ this.classB = classB } } class ClassB { var classC: ClassC constructor(classC: ClassC){ this.classC = classC } } class ClassC { constructor(){ } } class UserClass(){ fun doSomething(){ val classC = ClassC(); val classB = ClassB(classC); val classA = ClassA(classB); classA.someMethod(); } } view rawDI Example In Medium - 

Sekarang ClassA mendapatkan semua dependensi di dalam konstruktor dan cukup memanggil metode kelas ClassB tanpa menginisialisasi apa pun.


Manfaatnya


  • ClassA dan ClassB sekarang longgar digabungkan, dan kita dapat mengganti ClassB tanpa melanggar kode di dalam ClassA . Sebagai contoh, alih-alih melewati ClassB kita dapat melewati AssumeClassB , yang merupakan subkelas dari ClassB , dan program kami akan bekerja dengan baik.
  • ClassA sekarang dapat diuji. Saat menulis unit test, kita dapat membuat versi kita sendiri dari ClassB (objek uji) dan meneruskannya ke ClassA . Jika kesalahan terjadi saat lulus tes, sekarang kita tahu pasti bahwa ini pasti kesalahan di ClassA .
  • ClassB dibebaskan dari bekerja dengan dependensi dan dapat fokus pada tugasnya.

Kekurangan


  • Metode ini menyerupai mekanisme rantai, dan pada titik tertentu rantai harus terputus. Dengan kata lain, pengguna kelas ClassA harus tahu segalanya tentang inisialisasi ClassB , yang pada gilirannya membutuhkan pengetahuan tentang inisialisasi ClassC , dll. Jadi, Anda melihat bahwa setiap perubahan dalam konstruktor dari kelas-kelas ini dapat menyebabkan perubahan dalam kelas panggilan, belum lagi bahwa ClassA dapat memiliki lebih dari satu pengguna, sehingga logika membuat objek akan diulang.
  • Terlepas dari kenyataan bahwa dependensi kami jelas dan mudah dipahami, kode pengguna tidak trivial dan sulit dikelola. Karena itu, semuanya tidak begitu sederhana. Selain itu, kode melanggar prinsip tanggung jawab tunggal, karena bertanggung jawab tidak hanya untuk pekerjaannya, tetapi juga untuk implementasi dependensi di kelas-kelas dependen.

Metode kedua jelas bekerja lebih baik daripada yang pertama, tetapi masih memiliki kekurangannya. Apakah mungkin menemukan solusi yang lebih cocok? Sebelum mempertimbangkan cara ketiga, mari kita bicara tentang konsep injeksi ketergantungan.


Apa itu injeksi ketergantungan?


Injeksi ketergantungan adalah cara untuk menangani dependensi di luar kelas dependen ketika kelas dependen tidak perlu melakukan apa pun.

Berdasarkan definisi ini, solusi pertama kami jelas tidak menggunakan ide injeksi dependensi, dan cara kedua adalah bahwa kelas dependen tidak melakukan apa pun untuk menyediakan dependensi. Tapi kami masih berpikir solusi kedua itu buruk. MENGAPA?!


Karena definisi injeksi dependensi tidak mengatakan apa-apa tentang di mana pekerjaan dengan dependensi harus dilakukan (kecuali di luar kelas dependen), pengembang harus memilih tempat yang cocok untuk injeksi dependensi. Seperti yang Anda lihat dari contoh kedua, kelas pengguna bukan tempat yang tepat.


Bagaimana melakukan yang lebih baik? Mari kita lihat cara ketiga untuk menangani dependensi.


Cara ketiga: biarkan orang lain menangani dependensi daripada kita


Menurut pendekatan pertama, kelas dependen bertanggung jawab untuk mendapatkan dependensi mereka sendiri, dan pada pendekatan kedua, kami memindahkan pemrosesan dependensi dari kelas dependen ke kelas pengguna. Mari kita bayangkan bahwa ada orang lain yang bisa menangani dependensi, sebagai akibatnya baik kelas dependen maupun pengguna tidak akan melakukan pekerjaan. Metode ini memungkinkan Anda untuk bekerja dengan dependensi dalam aplikasi secara langsung.


Implementasi injeksi ketergantungan yang “bersih” (menurut pendapat pribadi saya)

Tanggung jawab untuk menangani dependensi ada pada pihak ketiga, jadi tidak ada bagian dari aplikasi yang akan berinteraksi dengannya.

Ketergantungan injeksi bukanlah teknologi, kerangka kerja, perpustakaan, atau sesuatu seperti itu. Ini hanya sebuah ide. Idenya adalah untuk bekerja dengan dependensi di luar kelas dependen (lebih disukai di bagian yang dialokasikan secara khusus). Anda dapat menerapkan ide ini tanpa menggunakan pustaka atau kerangka kerja apa pun. Namun, kami biasanya beralih ke kerangka kerja untuk menerapkan dependensi, karena menyederhanakan pekerjaan dan menghindari penulisan kode templat.


Setiap kerangka kerja injeksi ketergantungan memiliki dua karakteristik yang melekat. Fungsi tambahan lainnya mungkin tersedia untuk Anda, tetapi dua fungsi ini akan selalu ada:


Pertama, kerangka kerja ini menawarkan cara untuk menentukan bidang (objek) yang harus diimplementasikan. Beberapa kerangka kerja melakukan ini dengan @Inject keterangan bidang atau konstruktor menggunakan anotasi @Inject , tetapi ada metode lain. Misalnya, Koin menggunakan fitur bahasa bawaan Kotlin untuk menentukan implementasi. Inject berarti bahwa ketergantungan harus ditangani oleh kerangka kerja DI. Kode akan terlihat seperti ini:


 class ClassA { var classB: ClassB @Inject constructor(classB: ClassB){ this.classB = classB } } class ClassB { var classC: ClassC @Inject constructor(classC: ClassC){ this.classC = classC } } class ClassC { @Inject constructor(){ } } 

Kedua, kerangka kerja memungkinkan Anda untuk menentukan cara menyediakan setiap ketergantungan, dan ini terjadi dalam file terpisah. Kira-kira terlihat seperti ini (perlu diingat bahwa ini hanyalah contoh, dan mungkin berbeda dari kerangka ke kerangka kerja):


 class OurThirdPartyGuy { fun provideClassC(){ return ClassC() //just creating an instance of the object and return it. } fun provideClassB(classC: ClassC){ return ClassB(classC) } fun provideClassA(classB: ClassB){ return ClassA(classB) } } 

Jadi, seperti yang Anda lihat, setiap fungsi bertanggung jawab untuk memproses satu ketergantungan. Oleh karena itu, jika kita perlu menggunakan ClassA di suatu tempat dalam aplikasi, hal berikut akan terjadi: Kerangka kerja DI kita menciptakan satu instance dari kelas ClassC dengan memanggil provideClassC , meneruskannya ke provideClassB dan menerima instance dari ClassB , yang dilewatkan untuk provideClassA , dan sebagai hasilnya, ClassA dibuat. Ini hampir ajaib. Sekarang mari kita periksa kelebihan dan kelebihan dari metode ketiga.


Manfaatnya


  • Segalanya sesederhana mungkin. Baik kelas dependen dan kelas yang menyediakan dependensi jelas dan sederhana.
  • Kelas digabungkan secara longgar dan mudah diganti oleh kelas lain. Misalkan kita ingin mengganti ClassC dengan AssumeClassC , yang merupakan subkelas dari ClassC . Untuk melakukan ini, Anda hanya perlu mengubah kode penyedia sebagai berikut, dan di mana pun ClassC digunakan, versi baru sekarang akan digunakan secara otomatis:

 fun provideClassC(){ return AssumeClassC() } 

Harap dicatat bahwa tidak ada kode di dalam aplikasi yang berubah, hanya metode penyedia. Tampaknya tidak ada yang lebih sederhana dan lebih fleksibel.


  • Testabilitas yang luar biasa. Anda dapat dengan mudah mengganti dependensi dengan versi uji selama pengujian. Faktanya, injeksi ketergantungan adalah penolong utama Anda dalam hal pengujian.
  • Memperbaiki struktur kode, seperti aplikasi memiliki tempat terpisah untuk pemrosesan ketergantungan. Akibatnya, sisa aplikasi dapat fokus secara eksklusif pada fungsinya dan tidak tumpang tindih dengan dependensi.

Kekurangan


  • Kerangka kerja DI memiliki ambang masuk tertentu, sehingga tim proyek perlu menghabiskan waktu dan mempelajarinya sebelum menggunakannya secara efektif.

Kesimpulan


  • Penanganan ketergantungan tanpa DI dimungkinkan, tetapi hal itu dapat menyebabkan crash aplikasi.
  • DI hanyalah ide yang efektif, yang dengannya memungkinkan untuk menangani dependensi di luar kelas dependen.
  • Paling efektif menggunakan DI di bagian-bagian tertentu dari aplikasi. Banyak kerangka kerja berkontribusi untuk ini.
  • Kerangka kerja dan perpustakaan tidak diperlukan untuk DI, tetapi mereka dapat banyak membantu.

Dalam artikel ini, saya mencoba menjelaskan dasar-dasar bekerja dengan konsep injeksi ketergantungan, dan juga mencantumkan alasan untuk menggunakan ide ini. Ada banyak sumber daya yang dapat Anda jelajahi untuk mempelajari lebih lanjut tentang penggunaan DI di aplikasi Anda sendiri. Sebagai contoh, bagian terpisah di bagian lanjutan dari kursus profesi Android kami didedikasikan untuk topik ini.

Source: https://habr.com/ru/post/id434380/


All Articles