Saya membaca tentang injeksi ketergantungan (DI). Bagi saya, itu adalah hal yang sangat rumit untuk dilakukan, karena saya membaca itu juga merujuk pada inversi kontrol (IOC) dan saya merasa akan ikut dalam perjalanan.
Ini adalah pemahaman saya: Alih-alih membuat model di kelas yang juga mengkonsumsinya, Anda meneruskan (menyuntikkan) model (sudah diisi dengan properti menarik) ke tempat yang diperlukan (ke kelas baru yang bisa menjadikannya sebagai parameter dalam konstruktor).
Bagi saya, ini hanya lewat argumen. Aku pasti rindu mengerti maksudnya? Mungkin menjadi lebih jelas dengan proyek yang lebih besar?
Pemahaman saya adalah non-DI (menggunakan kode semu):
public void Start()
{
MyClass class = new MyClass();
}
...
public MyClass()
{
this.MyInterface = new MyInterface();
}
Dan DI akan menjadi
public void Start()
{
MyInterface myInterface = new MyInterface();
MyClass class = new MyClass(myInterface);
}
...
public MyClass(MyInterface myInterface)
{
this.MyInterface = myInterface;
}
Bisakah seseorang memberi penerangan karena saya yakin saya berada dalam kekacauan di sini.
sumber
Jawaban:
Ya, Anda menyuntikkan dependensi Anda, baik melalui konstruktor atau melalui properti.
Salah satu alasan untuk ini, bukan untuk membebani MyClass dengan rincian tentang bagaimana instance MyInterface perlu dibangun. MyInterface bisa menjadi sesuatu yang memiliki daftar seluruh dependensi dengan sendirinya dan kode MyClass akan menjadi sangat cepat jika Anda instantiate semua dependensi MyInterface di dalam MyClass.
Alasan lain adalah untuk pengujian.
Jika Anda memiliki ketergantungan pada antarmuka pembaca file dan Anda menyuntikkan dependensi ini melalui konstruktor di, katakanlah, ConsumerClass, itu berarti bahwa selama pengujian, Anda dapat melewati implementasi di-memori dari pembaca file ke ConsumerClass, menghindari kebutuhan untuk lakukan I / O selama pengujian.
sumber
Bagaimana DI dapat diimplementasikan sangat tergantung pada bahasa yang digunakan.
Berikut adalah contoh non-DI sederhana:
Ini menyebalkan, misalnya ketika untuk tes saya ingin menggunakan objek tiruan untuk
bar
. Jadi kita dapat membuat ini lebih fleksibel dan memungkinkan contoh untuk dilewatkan melalui konstruktor:Ini sudah merupakan bentuk injeksi ketergantungan yang paling sederhana. Tapi ini masih menyebalkan karena semuanya harus dilakukan secara manual (juga, karena penelepon dapat memegang referensi ke objek internal kita dan dengan demikian membatalkan keadaan kita).
Kami dapat memperkenalkan lebih banyak abstraksi dengan menggunakan pabrik:
Salah satu opsi adalah
Foo
agar dihasilkan oleh pabrik abstrakPilihan lain adalah menyerahkan pabrik ke konstruktor:
Masalah yang tersisa dengan ini adalah bahwa kita perlu menulis
DependencyManager
subkelas baru untuk setiap konfigurasi, dan bahwa jumlah dependensi yang dapat dikelola cukup terbatas (setiap ketergantungan baru membutuhkan metode baru di antarmuka).Dengan fitur seperti refleksi dan pemuatan kelas dinamis, kita dapat mengelak dari ini. Tetapi ini sangat tergantung pada bahasa yang digunakan. Di Perl, kelas dapat dirujuk dengan nama mereka, dan saya bisa melakukannya
Dalam bahasa seperti Java, saya bisa membuat manajer dependensi berperilaku mirip dengan
Map<Class, Object>
:Untuk kelas aktual mana yang
Bar.class
dipecahkan dapat dikonfigurasikan saat runtime, misalnya dengan mempertahankanMap<Class, Class>
antarmuka yang akan diimplementasikan.Masih ada elemen manual yang terlibat dalam penulisan konstruktor. Tetapi kita dapat membuat konstruktor sama sekali tidak perlu, misalnya dengan menggerakkan DI melalui anotasi:
Berikut ini adalah contoh implementasi kerangka kerja DI yang kecil (dan sangat terbatas): http://ideone.com/b2ubuF , meskipun implementasi ini benar-benar tidak dapat digunakan untuk objek yang tidak dapat diubah (implementasi naif ini tidak dapat mengambil parameter apa pun untuk konstruktor).
sumber
Teman-teman kami di Stack Overflow memiliki jawaban yang bagus untuk ini . Favorit saya adalah jawaban kedua, termasuk kutipan:
dari blog James Shore . Tentu saja, ada versi / pola yang lebih rumit berlapis-lapis di atas ini, tetapi pada dasarnya cukup untuk memahami apa yang sedang terjadi. Alih-alih objek membuat variabel contoh sendiri, mereka diteruskan dari luar.
sumber
Katakanlah Anda sedang melakukan desain interior di ruang tamu Anda: Anda memasang lampu gantung mewah di langit-langit dan memasang lampu lantai yang serasi. Setahun kemudian, istrimu yang cantik memutuskan dia menginginkan ruangan itu sedikit kurang formal. Perlengkapan pencahayaan mana yang lebih mudah diubah?
Gagasan di balik DI adalah menggunakan pendekatan "plug-in" di mana-mana. Ini mungkin tidak terlihat seperti masalah besar jika Anda tinggal di gubuk sederhana tanpa drywall dan semua kabel listrik terbuka. (Anda seorang tukang - Anda dapat mengubah apa pun!) Dan juga, pada aplikasi kecil & sederhana, DI dapat menambah lebih banyak kerumitan daripada nilainya.
Tetapi untuk aplikasi yang besar dan kompleks, DI sangat diperlukan untuk pemeliharaan jangka panjang. Ini memungkinkan Anda untuk "mencabut" seluruh modul dari kode Anda (database backend, misalnya), dan menukar mereka dengan modul yang berbeda yang mencapai hal yang sama tetapi melakukannya dengan cara yang sama sekali berbeda (misalnya sistem penyimpanan cloud).
BTW, diskusi tentang DI tidak akan lengkap tanpa rekomendasi Dependency Injection dalam .NET , oleh Mark Seeman. Jika Anda terbiasa dengan .NET dan Anda pernah ingin terlibat dalam pengembangan SW skala besar, ini adalah bacaan penting. Dia menjelaskan konsepnya jauh lebih baik daripada yang saya bisa.
Biarkan saya meninggalkan Anda dengan satu karakteristik penting terakhir DI yang contoh kode Anda abaikan. DI memungkinkan Anda untuk memanfaatkan potensi fleksibilitas yang sangat besar yang dirangkum dalam pepatah " Program untuk antarmuka ". Untuk sedikit memodifikasi contoh Anda:
Tapi SEKARANG, karena
MyRoom
dirancang untuk antarmuka ILightFixture , saya dapat dengan mudah masuk tahun depan dan mengubah satu baris dalam fungsi Utama ("menyuntikkan" ketergantungan "yang berbeda"), dan saya langsung mendapatkan fungsionalitas baru di MyRoom (tanpa harus membangun kembali atau memindahkan MyRoom, jika kebetulan berada di perpustakaan terpisah. Ini tentu saja mengasumsikan bahwa semua implementasi ILightFixture Anda dirancang dengan mempertimbangkan Pergantian Liskov). Semua, hanya dengan perubahan sederhana:Plus, sekarang saya bisa Unit Uji
MyRoom
(Anda adalah unit testing, kan?) Dan menggunakan "tiruan" lampu, yang memungkinkan saya untuk mengujiMyRoom
sepenuhnya independen dariClassyChandelier.
Ada banyak keuntungan, tentu saja, tetapi ini adalah orang-orang yang menjual ide kepada saya.
sumber
Ketergantungan injeksi hanya melewati parameter, tetapi itu masih mengarah ke beberapa masalah, dan namanya dimaksudkan untuk sedikit fokus pada mengapa perbedaan antara membuat objek vs melewati satu adalah penting.
Ini penting karena (cukup jelas) setiap objek waktu A memanggil tipe B, A bergantung pada cara B bekerja. Yang berarti bahwa jika A membuat keputusan untuk tipe B beton mana yang akan digunakan, maka banyak fleksibilitas dalam bagaimana A dapat digunakan oleh kelas luar, tanpa memodifikasi A, telah hilang.
Saya menyebutkan ini karena Anda berbicara tentang "kehilangan titik" injeksi ketergantungan, dan ini adalah sebagian besar alasan mengapa orang suka melakukannya. Ini mungkin hanya berarti melewati parameter, tetapi apakah Anda melakukannya dapat menjadi penting.
Juga, beberapa kesulitan dalam mengimplementasikan DI memang terlihat lebih baik di proyek-proyek besar. Anda umumnya akan memindahkan keputusan aktual dari kelas konkret mana yang akan digunakan sampai ke puncak, sehingga metode awal Anda akan terlihat seperti:
tetapi dengan 400 baris lain dari jenis kode memukau ini. Jika Anda membayangkan mempertahankannya dari waktu ke waktu daya tarik wadah DI menjadi lebih jelas.
Juga, bayangkan kelas yang, katakanlah, mengimplementasikan IDisposable. Jika kelas-kelas yang menggunakannya mendapatkannya melalui injeksi daripada membuatnya sendiri, bagaimana mereka tahu kapan harus memanggil Buang ()? (Yang merupakan masalah lain yang berhubungan dengan wadah DI.)
Jadi, tentu saja, ketergantungan injeksi hanya melewati parameter, tetapi benar-benar ketika orang mengatakan injeksi ketergantungan mereka berarti "mengirimkan parameter ke objek Anda vs alternatif memiliki objek Anda instantiate sesuatu itu sendiri", dan berurusan dengan semua manfaat dari kelemahan dari pendekatan semacam itu, mungkin dengan bantuan wadah DI.
sumber
Di tingkat kelas, itu mudah.
'Injeksi Ketergantungan' hanya menjawab pertanyaan "bagaimana saya akan menemukan kolaborator saya" dengan "mereka didorong pada Anda - Anda tidak harus pergi dan mendapatkannya sendiri". (Ini mirip dengan - tetapi tidak sama dengan - 'Pembalikan Kontrol' di mana pertanyaan "bagaimana saya akan memesan operasi saya atas input saya?" Memiliki jawaban yang sama).
Satu- satunya manfaat yang mendorong kolaborator Anda pada Anda adalah yang memungkinkan kode klien untuk menggunakan kelas Anda untuk membuat grafik objek yang sesuai dengan kebutuhan saat ini ... Anda belum secara sewenang-wenang menentukan bentuk dan mutabilitas grafik dengan memutuskan secara pribadi jenis konkret dan siklus hidup kolaborator Anda.
(Semua manfaat lainnya, dari testability, lepas kopling, dll, sebagian besar mengikuti dari penggunaan antarmuka dan tidak begitu banyak dari ketergantungan-injeksi-ness, meskipun DI secara alami mempromosikan penggunaan antarmuka).
Perlu dicatat bahwa, jika Anda menghindari instantiate kolaborator Anda sendiri, kelas Anda karenanya harus mendapatkan kolaboratornya dari konstruktor, properti, atau argumen metode (opsi terakhir ini sering diabaikan, omong-omong ... cara itu memang terjadi. tidak selalu masuk akal bagi kelas 'kolaborator untuk menjadi bagian dari' keadaannya ').
Dan itu bagus.
Pada tingkat aplikasi ...
Begitu banyak untuk pandangan per kelas tentang hal-hal. Katakanlah Anda memiliki banyak kelas yang mengikuti aturan "jangan instantiate kolaborator Anda sendiri", dan ingin membuat aplikasi dari mereka. Hal paling sederhana yang harus dilakukan adalah menggunakan kode lama yang baik (alat yang sangat berguna untuk memanggil konstruktor, properti, dan metode!) Untuk menyusun grafik objek yang Anda inginkan, dan memberikan beberapa masukan padanya. (Ya, beberapa objek dalam grafik Anda sendiri akan menjadi pabrik objek, yang telah diedarkan sebagai kolaborator ke objek berumur panjang lainnya dalam grafik, siap untuk digunakan ... Anda tidak dapat melakukan pra-konstruksi setiap objek! ).
... kebutuhan Anda untuk 'secara fleksibel' mengkonfigurasi objek-grafik aplikasi Anda ...
Bergantung pada tujuan Anda yang lain (yang tidak berhubungan dengan kode), Anda mungkin ingin memberi pengguna akhir kontrol atas grafik objek yang digunakan. Ini mengarahkan Anda ke arah skema konfigurasi, dari satu jenis atau lainnya, apakah itu file teks dari desain Anda sendiri dengan beberapa pasangan nama / nilai, file XML, DSL kustom, bahasa deskripsi grafik deklaratif seperti YAML, bahasa skrip imperatif seperti JavaScript, atau sesuatu yang lain yang sesuai dengan tugas yang dihadapi. Apa pun yang diperlukan untuk membuat grafik objek yang valid, dengan cara yang memenuhi kebutuhan pengguna Anda.
... mungkin kekuatan desain yang signifikan.
Dalam keadaan yang paling ekstrem dari tipe itu, Anda dapat memilih untuk mengambil pendekatan yang sangat umum, dan memberi pengguna akhir mekanisme umum untuk 'memasang kabel' grafik objek yang mereka pilih dan bahkan memungkinkan mereka untuk memberikan realisasi konkret dari antarmuka ke runtime! (Dokumentasi Anda adalah permata yang berkilau, pengguna Anda sangat pintar, akrab dengan setidaknya garis besar kasar dari grafik objek aplikasi Anda, tetapi tidak memiliki kompiler yang praktis). Skenario ini secara teoritis dapat terjadi dalam beberapa situasi 'perusahaan'.
Dalam hal ini, Anda mungkin memiliki bahasa deklaratif yang memungkinkan pengguna Anda untuk mengekspresikan jenis apa pun, komposisi grafik objek jenis tersebut, dan palet antarmuka yang dapat dicampur dan dicocokkan oleh pengguna akhir mitos. Untuk mengurangi beban kognitif pada pengguna Anda, Anda lebih suka pendekatan 'konfigurasi dengan konvensi', sehingga mereka hanya perlu melangkah dan menaiki objek-grafik-fragmen minat, daripada bergulat dengan semuanya.
Dasar kau orang miskin!
Karena Anda tidak suka menulis semua itu sendiri (tapi serius, lihat mengikat YAML untuk bahasa Anda), Anda menggunakan semacam kerangka DI.
Bergantung pada kematangan kerangka kerja itu, Anda mungkin tidak memiliki opsi untuk menggunakan konstruktor-injeksi, bahkan ketika itu masuk akal (kolaborator tidak berubah selama masa objek), sehingga memaksa Anda untuk menggunakan Setter Injection (bahkan ketika kolaborator tidak berubah selama masa objek, dan bahkan ketika tidak ada alasan logis mengapa semua implementasi konkret antarmuka harus memiliki kolaborator dari tipe tertentu). Jika demikian, Anda saat ini berada dalam neraka penggabungan yang kuat, meskipun telah rajin 'menggunakan antarmuka' di seluruh basis kode Anda - horor!
Semoga saja, Anda menggunakan kerangka kerja DI yang memberi Anda opsi injeksi konstruktor, dan pengguna Anda hanya sedikit pemarah pada Anda karena tidak menghabiskan lebih banyak waktu untuk memikirkan hal-hal spesifik yang mereka butuhkan untuk mengonfigurasi dan memberi mereka UI yang lebih cocok untuk tugas tersebut. di tangan. (Meskipun untuk bersikap adil, Anda mungkin memang mencoba memikirkan cara, tetapi JavaEE mengecewakan Anda dan Anda harus menggunakan hack yang mengerikan ini).
Bootnote
Pada titik mana pun Anda tidak pernah menggunakan Google Guice, yang memberi Anda pembuat kode cara untuk menghilangkan tugas menyusun objek-grafik dengan kode ... dengan menulis kode. Argh!
sumber
Banyak orang mengatakan di sini bahwa DI adalah mekanisme untuk meng-out-source inisialisasi variabel instan.
Untuk contoh yang jelas dan sederhana, Anda tidak benar-benar membutuhkan "injeksi ketergantungan". Anda dapat melakukan ini dengan parameter sederhana yang dikirimkan ke konstruktor, seperti yang Anda catat dalam pertanyaan.
Namun, saya pikir mekanisme "injeksi ketergantungan" khusus berguna ketika Anda berbicara tentang sumber daya tingkat aplikasi, di mana penelepon dan callee tidak tahu apa-apa tentang sumber daya ini (dan tidak ingin tahu!).
Misalnya: Koneksi basis data, Konteks keamanan, Fasilitas logging, File konfigurasi, dll.
Selain itu, secara umum setiap singleton adalah kandidat yang baik untuk DI. Semakin banyak yang Anda miliki dan gunakan, semakin banyak peluang yang Anda butuhkan DI.
Untuk jenis sumber daya ini cukup jelas bahwa tidak masuk akal untuk memindahkan mereka semua melalui daftar parameter dan oleh karena itu DI diciptakan.
Catatan terakhir, Anda juga memerlukan wadah DI, yang akan melakukan injeksi yang sebenarnya ... (sekali lagi, untuk melepaskan penelepon dan meninggalkan detail implementasi).
sumber
Jika Anda memasukkan tipe referensi abstrak maka kode panggilan dapat melewati objek apa pun yang memperluas / mengimplementasikan tipe abstrak. Jadi di masa depan kelas yang menggunakan objek bisa menggunakan objek baru yang telah dibuat tanpa pernah memodifikasi kodenya.
sumber
Ini adalah pilihan lain selain jawaban amon
Gunakan Pembangun:
Ini yang saya gunakan untuk dependensi. Saya tidak menggunakan kerangka kerja DI. Mereka menghalangi.
sumber