Saya mencoba memahami injeksi ketergantungan (DI), dan sekali lagi saya gagal. Sepertinya konyol. Kode saya tidak pernah berantakan; Saya jarang menulis fungsi dan antarmuka virtual (walaupun saya lakukan sekali dalam bulan biru) dan semua konfigurasi saya secara ajaib disambungkan ke dalam kelas menggunakan json.net (kadang-kadang menggunakan serializer XML).
Saya tidak begitu mengerti masalah apa yang dipecahkannya. Sepertinya cara untuk mengatakan: "Hai. Ketika Anda menjalankan fungsi ini, kembalikan objek yang jenis ini dan gunakan parameter / data ini."
Tapi ... mengapa saya menggunakannya? Catatan Saya tidak pernah perlu menggunakan object
juga, tapi saya mengerti untuk apa itu.
Apa saja situasi nyata dalam membangun situs web atau aplikasi desktop di mana orang akan menggunakan DI? Saya dapat menemukan case dengan mudah mengapa seseorang mungkin ingin menggunakan antarmuka / fungsi virtual dalam sebuah game, tetapi sangat jarang (cukup langka sehingga saya tidak dapat mengingat satu contoh) untuk menggunakannya dalam kode non-game.
sumber
Jawaban:
Pertama, saya ingin menjelaskan asumsi yang saya buat untuk jawaban ini. Itu tidak selalu benar, tetapi cukup sering:
(Sebenarnya, ada antarmuka yang juga merupakan kata benda, tapi saya ingin menggeneralisasi sini.)
Jadi, misalnya suatu antarmuka dapat berupa sesuatu seperti
IDisposable
,IEnumerable
atauIPrintable
. Kelas adalah implementasi aktual dari satu atau lebih antarmuka ini:List
atauMap
keduanya bisa merupakan implementasi dariIEnumerable
.Untuk mendapatkan intinya: Seringkali kelas Anda bergantung satu sama lain. Misalnya Anda dapat memiliki
Database
kelas yang mengakses database Anda (hah, kejutan! ;-)), tetapi Anda juga ingin kelas ini melakukan logging tentang mengakses database. Misalkan Anda memiliki kelas lainLogger
, makaDatabase
memiliki ketergantungan padaLogger
.Sejauh ini bagus.
Anda bisa memodelkan ketergantungan ini di dalam
Database
kelas Anda dengan baris berikut:dan semuanya baik-baik saja. Tidak apa-apa sampai hari ketika Anda menyadari bahwa Anda memerlukan banyak penebang: Kadang-kadang Anda ingin masuk ke konsol, kadang-kadang ke sistem file, kadang-kadang menggunakan TCP / IP dan server logging jarak jauh, dan seterusnya ...
Dan tentu saja Anda TIDAK ingin mengubah semua kode Anda (sementara itu Anda memiliki gazillions) dan mengganti semua baris
oleh:
Pertama, ini tidak menyenangkan. Kedua, ini rawan kesalahan. Ketiga, ini adalah pekerjaan yang bodoh dan berulang-ulang untuk monyet yang terlatih. Jadi apa yang kamu lakukan?
Jelas itu ide yang cukup bagus untuk memperkenalkan antarmuka
ICanLog
(atau serupa) yang diterapkan oleh semua berbagai penebang. Jadi langkah 1 dalam kode Anda adalah yang Anda lakukan:Sekarang jenis inferensi tidak mengubah jenis lagi, Anda selalu memiliki satu antarmuka untuk dikembangkan. Langkah selanjutnya adalah Anda tidak ingin
new Logger()
berulang kali. Jadi, Anda menempatkan keandalan untuk membuat instance baru ke satu, kelas pabrik pusat, dan Anda mendapatkan kode seperti:Pabrik itu sendiri memutuskan jenis logger apa yang akan dibuat. Kode Anda tidak peduli lagi, dan jika Anda ingin mengubah jenis logger yang digunakan, Anda mengubahnya sekali : Di dalam pabrik.
Sekarang, tentu saja, Anda dapat menggeneralisasi pabrik ini, dan membuatnya berfungsi untuk semua jenis:
Di suatu tempat TypeFactory ini membutuhkan data konfigurasi yang kelas aktual untuk instantiate ketika jenis antarmuka tertentu diminta, sehingga Anda memerlukan pemetaan. Tentu saja Anda dapat melakukan pemetaan ini di dalam kode Anda, tetapi kemudian perubahan jenis berarti kompilasi ulang. Tapi Anda juga bisa meletakkan pemetaan ini di dalam file XML, mis. Ini memungkinkan Anda untuk mengubah kelas yang sebenarnya digunakan bahkan setelah waktu kompilasi (!), Itu berarti secara dinamis, tanpa kompilasi ulang!
Untuk memberi Anda contoh yang berguna untuk ini: Pikirkan perangkat lunak yang tidak masuk secara normal, tetapi ketika pelanggan Anda menelepon dan meminta bantuan karena ia memiliki masalah, semua yang Anda kirim kepadanya adalah file konfigurasi XML yang diperbarui, dan sekarang ia memiliki logging diaktifkan, dan dukungan Anda dapat menggunakan file log untuk membantu pelanggan Anda.
Dan sekarang, ketika Anda mengganti nama sedikit, Anda berakhir dengan implementasi sederhana dari Service Locator , yang merupakan salah satu dari dua pola untuk Inversion of Control (karena Anda membalikkan kontrol atas siapa yang memutuskan kelas yang tepat untuk instantiate).
Semua ini mengurangi ketergantungan pada kode Anda, tetapi sekarang semua kode Anda memiliki ketergantungan pada pusat layanan tunggal.
Injeksi ketergantungan sekarang merupakan langkah berikutnya dalam baris ini: Cukup singkirkan ketergantungan tunggal ini ke pencari layanan: Daripada berbagai kelas yang meminta pencari layanan untuk implementasi untuk antarmuka tertentu, Anda - sekali lagi - kembalikan kontrol atas siapa yang memberi instantiasi apa .
Dengan injeksi dependensi,
Database
kelas Anda sekarang memiliki konstruktor yang memerlukan parameter tipeICanLog
:Sekarang database Anda selalu memiliki logger untuk digunakan, tetapi tidak tahu lagi dari mana logger ini berasal.
Dan di sinilah kerangka kerja DI berperan: Anda mengkonfigurasi pemetaan Anda sekali lagi, dan kemudian meminta kerangka kerja DI Anda untuk instantiate aplikasi Anda untuk Anda. Karena
Application
kelas membutuhkanICanPersistData
implementasi, instance dariDatabase
disuntikkan - tetapi untuk itu ia harus terlebih dahulu membuat instance dari jenis logger yang dikonfigurasi untukICanLog
. Dan seterusnya ...Jadi, untuk mempersingkat cerita: Injeksi ketergantungan adalah salah satu dari dua cara bagaimana menghapus dependensi dalam kode Anda. Ini sangat berguna untuk perubahan konfigurasi setelah waktu kompilasi, dan itu adalah hal yang hebat untuk pengujian unit (karena membuatnya sangat mudah untuk menyuntikkan bertopik dan / atau mengolok-olok).
Dalam praktiknya, ada hal-hal yang tidak dapat Anda lakukan tanpa pelacak layanan (misalnya, jika Anda tidak tahu sebelumnya berapa banyak instance yang Anda perlukan dari antarmuka tertentu: Kerangka kerja DI selalu menyuntikkan hanya satu instance per parameter, tetapi Anda dapat memanggil pelacak layanan di dalam satu lingkaran, tentu saja), maka paling sering setiap kerangka kerja DI juga menyediakan pencari layanan.
Tetapi pada dasarnya, itu saja.
PS: Apa yang saya jelaskan di sini adalah teknik yang disebut injeksi konstruktor , ada juga injeksi properti di mana bukan parameter konstruktor, tetapi properti sedang digunakan untuk mendefinisikan dan menyelesaikan dependensi. Pikirkan injeksi properti sebagai dependensi opsional, dan injeksi konstruktor sebagai dependensi wajib. Tetapi diskusi tentang hal ini berada di luar cakupan pertanyaan ini.
sumber
Saya pikir banyak kali orang menjadi bingung tentang perbedaan antara injeksi ketergantungan dan kerangka kerja injeksi ketergantungan (atau wadah seperti yang sering disebut).
Injeksi ketergantungan adalah konsep yang sangat sederhana. Alih-alih kode ini:
Anda menulis kode seperti ini:
Dan itu saja. Serius. Ini memberi Anda banyak keuntungan. Dua yang penting adalah kemampuan untuk mengontrol fungsionalitas dari tempat pusat (
Main()
fungsi) alih-alih menyebarkannya di seluruh program Anda, dan kemampuan untuk lebih mudah menguji setiap kelas secara terpisah (karena Anda dapat memasukkan tiruan atau benda palsu lainnya ke dalam konstruktornya sebagai gantinya dari nilai nyata).Kekurangannya, tentu saja, adalah bahwa Anda sekarang memiliki satu mega fungsi yang tahu tentang semua kelas yang digunakan oleh program Anda. Itulah yang dapat membantu kerangka kerja DI. Tetapi jika Anda mengalami kesulitan memahami mengapa pendekatan ini berharga, saya sarankan mulai dengan injeksi ketergantungan manual terlebih dahulu, sehingga Anda dapat lebih menghargai apa yang bisa dilakukan berbagai kerangka kerja untuk Anda.
sumber
Seperti jawaban lain yang dinyatakan, injeksi dependensi adalah cara untuk membuat dependensi Anda di luar kelas yang menggunakannya. Anda menyuntikkan mereka dari luar, dan mengambil kendali tentang ciptaan mereka dari dalam kelas Anda. Ini juga mengapa injeksi ketergantungan merupakan realisasi dari prinsip Inversion of control (IoC).
IoC adalah prinsipnya, di mana DI adalah polanya. Alasan bahwa Anda mungkin "membutuhkan lebih dari satu penebang" tidak pernah benar-benar bertemu, sejauh pengalaman saya, tetapi alasan sebenarnya adalah, bahwa Anda benar-benar membutuhkannya, setiap kali Anda menguji sesuatu. Sebuah contoh:
Fitur saya:
Anda dapat menguji ini seperti ini:
Jadi di suatu tempat di
OfferWeasel
, itu membuat Anda objek tawaran seperti ini:Masalahnya di sini adalah, bahwa tes ini kemungkinan besar akan selalu gagal, karena tanggal yang ditetapkan akan berbeda dari tanggal yang ditetapkan, bahkan jika Anda hanya memasukkan
DateTime.Now
kode tes itu mungkin dimatikan oleh beberapa milidetik dan karenanya akan selalu gagal. Solusi yang lebih baik sekarang adalah membuat antarmuka untuk ini, yang memungkinkan Anda untuk mengontrol waktu yang akan ditetapkan:Antarmuka adalah abstraksi. Satu adalah hal yang NYATA, dan yang lain memungkinkan Anda untuk memalsukan waktu di mana diperlukan. Tes kemudian dapat diubah seperti ini:
Seperti ini, Anda menerapkan prinsip "inversi kontrol", dengan menyuntikkan dependensi (mendapatkan waktu sekarang). Alasan utama untuk melakukan ini adalah untuk pengujian unit terisolasi yang lebih mudah, ada cara lain untuk melakukannya. Sebagai contoh, sebuah antarmuka dan kelas di sini tidak perlu karena dalam fungsi C # dapat dilewatkan sebagai variabel, jadi alih-alih sebuah antarmuka Anda dapat menggunakan a
Func<DateTime>
untuk mencapai yang sama. Atau, jika Anda mengambil pendekatan dinamis, Anda hanya melewatkan objek apa pun yang memiliki metode setara ( bebek mengetik ), dan Anda tidak memerlukan antarmuka sama sekali.Anda hampir tidak pernah membutuhkan lebih dari satu logger. Meskipun demikian, ketergantungan injeksi sangat penting untuk kode yang diketik secara statis seperti Java atau C #.
Dan ... Juga harus dicatat bahwa suatu objek hanya dapat memenuhi tujuannya dengan tepat pada saat runtime, jika semua dependensinya tersedia, sehingga tidak banyak digunakan dalam mengatur injeksi properti. Menurut pendapat saya, semua dependensi harus dipenuhi ketika konstruktor dipanggil, jadi konstruktor-injeksi adalah hal yang harus dilakukan.
Saya harap itu membantu.
sumber
Saya pikir jawaban klasik adalah untuk membuat aplikasi yang lebih dipisahkan, yang tidak memiliki pengetahuan tentang implementasi yang akan digunakan selama runtime.
Misalnya, kami adalah penyedia pembayaran pusat, bekerja dengan banyak penyedia pembayaran di seluruh dunia. Namun, ketika ada permintaan, saya tidak tahu prosesor pembayaran mana yang akan saya hubungi. Saya bisa memprogram satu kelas dengan satu ton sakelar, seperti:
Sekarang bayangkan bahwa sekarang Anda harus mempertahankan semua kode ini dalam satu kelas karena tidak dipisahkan dengan benar, Anda dapat membayangkan bahwa untuk setiap prosesor baru yang Anda dukung, Anda harus membuat yang baru jika // beralih case untuk setiap metode, ini hanya menjadi lebih rumit, bagaimanapun, dengan menggunakan Dependency Injection (atau Inversion of Control - seperti yang kadang-kadang disebut, yang berarti bahwa siapa pun yang mengendalikan jalannya program hanya dikenal pada saat runtime, dan bukan komplikasi), Anda dapat mencapai sesuatu sangat rapi dan terawat.
** Kode tidak akan dikompilasi, saya tahu :)
sumber
new ThatProcessor()
menggunakannya sebagaiAlasan utama untuk menggunakan DI adalah karena Anda ingin menempatkan tanggung jawab atas pengetahuan implementasi di mana pengetahuan itu ada. Ide DI sangat sejalan dengan enkapsulasi dan desain oleh antarmuka. Jika ujung depan meminta dari belakang untuk beberapa data, maka apakah tidak penting untuk ujung depan bagaimana ujung belakang menyelesaikan pertanyaan itu. Terserah peminta permintaan.
Itu sudah umum di OOP untuk waktu yang lama. Sering kali membuat potongan kode seperti:
Kekurangannya adalah bahwa kelas implementasi masih hardcoded, maka dari itu memiliki ujung depan pengetahuan yang implementasi digunakan. DI mengambil desain dengan antarmuka selangkah lebih maju, bahwa satu-satunya hal yang perlu diketahui ujung depan adalah pengetahuan tentang antarmuka. Di antara DYI dan DI adalah pola locator layanan, karena ujung depan harus menyediakan kunci (hadir dalam registri locator layanan) untuk memungkinkan permintaannya diselesaikan. Contoh pencari layanan:
Contoh DI:
Salah satu persyaratan DI adalah bahwa wadah harus dapat mengetahui kelas mana yang merupakan implementasi dari antarmuka mana. Oleh karena itu apakah wadah DI memerlukan desain yang sangat diketik dan hanya satu implementasi untuk setiap antarmuka pada saat yang sama. Jika Anda memerlukan lebih banyak implementasi antarmuka pada saat yang sama (seperti kalkulator), Anda memerlukan pencari lokasi atau pola desain pabrik.
D (b) I: Injeksi Ketergantungan dan Desain berdasarkan Antarmuka. Pembatasan ini bukan masalah praktis yang sangat besar. Manfaat menggunakan D (b) I adalah melayani komunikasi antara klien dan penyedia. Antarmuka adalah perspektif pada suatu objek atau serangkaian perilaku. Yang terakhir ini sangat penting di sini.
Saya lebih suka administrasi kontrak layanan bersama dengan D (b) I dalam pengkodean. Mereka harus pergi bersama. Penggunaan D (b) I sebagai solusi teknis tanpa administrasi organisasi dari kontrak layanan tidak terlalu menguntungkan menurut saya, karena DI kemudian hanyalah lapisan enkapsulasi tambahan. Tetapi ketika Anda dapat menggunakannya bersama-sama dengan administrasi organisasi Anda benar-benar dapat menggunakan prinsip pengorganisasian D (b) yang saya tawarkan. Ini dapat membantu Anda dalam jangka panjang untuk menyusun komunikasi dengan klien dan departemen teknis lainnya dalam topik-topik seperti pengujian, versi, dan pengembangan alternatif. Ketika Anda memiliki antarmuka implisit seperti dalam kelas hardcoded, maka itu jauh lebih sedikit dari waktu ke waktu kemudian ketika Anda membuatnya eksplisit menggunakan D (b) I. Itu semua bermuara pada pemeliharaan, yang dari waktu ke waktu dan tidak pada suatu waktu. :-)
sumber