Pertama, saya ingin menjelaskan asumsi yang saya buat untuk jawaban ini. Itu tidak selalu benar, tetapi cukup sering:
Antarmuka adalah kata sifat; kelas adalah kata benda.
(Sebenarnya, ada antarmuka yang juga merupakan kata benda, tapi saya ingin menggeneralisasi sini.)
Jadi, misalnya suatu antarmuka dapat berupa sesuatu seperti IDisposable
, IEnumerable
atau IPrintable
. Kelas adalah implementasi aktual dari satu atau lebih antarmuka ini: List
atau Map
keduanya bisa merupakan implementasi dari IEnumerable
.
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 lain Logger
, maka Database
memiliki ketergantungan pada Logger
.
Sejauh ini bagus.
Anda bisa memodelkan ketergantungan ini di dalam Database
kelas Anda dengan baris berikut:
var logger = new Logger();
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
var logger = new Logger();
oleh:
var logger = new TcpLogger();
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:
ICanLog logger = new Logger();
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:
ICanLog logger = LoggerFactory.Create();
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:
ICanLog logger = TypeFactory.Create<ICanLog>();
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 tipe ICanLog
:
public Database(ICanLog logger) { ... }
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 membutuhkan ICanPersistData
implementasi, instance dari Database
disuntikkan - tetapi untuk itu ia harus terlebih dahulu membuat instance dari jenis logger yang dikonfigurasi untuk ICanLog
. 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.