Kerangka kerja injeksi ketergantungan seperti Google Guice memberikan motivasi berikut untuk penggunaannya ( sumber ):
Untuk membangun objek, pertama-tama Anda membangun dependensinya. Tetapi untuk membangun setiap ketergantungan, Anda membutuhkan dependensinya, dan sebagainya. Jadi ketika Anda membangun objek, Anda benar-benar perlu membuat grafik objek.
Membuat grafik objek dengan tangan membutuhkan banyak tenaga (...) dan membuat pengujian menjadi sulit.
Tapi saya tidak membeli argumen ini: Bahkan tanpa kerangka kerja ketergantungan injeksi, saya bisa menulis kelas yang mudah untuk instantiate dan nyaman untuk diuji. Misalnya, contoh dari halaman motivasi Guice dapat ditulis ulang dengan cara berikut:
class BillingService
{
private final CreditCardProcessor processor;
private final TransactionLog transactionLog;
// constructor for tests, taking all collaborators as parameters
BillingService(CreditCardProcessor processor, TransactionLog transactionLog)
{
this.processor = processor;
this.transactionLog = transactionLog;
}
// constructor for production, calling the (productive) constructors of the collaborators
public BillingService()
{
this(new PaypalCreditCardProcessor(), new DatabaseTransactionLog());
}
public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard)
{
...
}
}
Jadi mungkin ada argumen lain untuk kerangka kerja injeksi ketergantungan ( yang berada di luar cakupan untuk pertanyaan ini !), Tetapi pembuatan grafik objek yang mudah diuji bukan salah satunya, bukan?
new ShippingService()
.Jawaban:
Ada perdebatan lama yang terus-menerus tentang cara terbaik untuk melakukan injeksi ketergantungan.
Potongan asli pegas yang dipakai objek polos, dan kemudian menyuntikkan dependensi melalui metode setter.
Tetapi kemudian sejumlah besar orang bersikeras bahwa menyuntikkan dependensi melalui parameter konstruktor adalah cara yang tepat untuk melakukannya.
Kemudian, akhir-akhir ini, ketika menggunakan refleksi menjadi lebih umum, menetapkan nilai-nilai anggota pribadi secara langsung, tanpa argumen setters, menjadi kemarahan.
Jadi konstruktor pertama Anda konsisten dengan pendekatan kedua untuk injeksi ketergantungan. Hal ini memungkinkan Anda untuk melakukan hal-hal bagus seperti menyuntikkan mock untuk pengujian.
Tetapi konstruktor no-argumen memiliki masalah ini. Karena ini adalah instantiate kelas implementasi untuk
PaypalCreditCardProcessor
danDatabaseTransactionLog
, itu menciptakan ketergantungan, waktu kompilasi yang sulit pada PayPal dan Database. Dibutuhkan tanggung jawab untuk membangun dan mengonfigurasi seluruh pohon dependensi dengan benar.Bayangkan bahwa prosesor PayPay adalah subsistem yang sangat rumit, dan juga menarik banyak perpustakaan pendukung. Dengan membuat ketergantungan waktu kompilasi pada kelas implementasi itu, Anda membuat tautan yang tidak bisa dipecahkan ke seluruh pohon dependensi itu. Kompleksitas grafik objek Anda baru saja melonjak dengan urutan besarnya, mungkin dua.
Banyak dari item-item di pohon dependensi akan transparan, tetapi banyak dari mereka juga perlu instantiated. Kemungkinannya adalah, Anda tidak akan bisa hanya instantiate a
PaypalCreditCardProcessor
.Selain instantiasi, masing-masing objek akan membutuhkan properti yang diterapkan dari konfigurasi.
Jika Anda hanya memiliki ketergantungan pada antarmuka, dan memungkinkan pabrik eksternal untuk membangun dan menyuntikkan dependensi, mereka yang Anda potong seluruh pohon ketergantungan PayPal, dan kompleksitas kode Anda berhenti di antarmuka.
Ada manfaat lain, seperti untuk menentukan kelas implementasi di dalam konfigurasi (yaitu pada saat runtime daripada waktu kompilasi), atau memiliki spesifikasi ketergantungan yang lebih dinamis yang bervariasi, misalnya, berdasarkan lingkungan (pengujian, integrasi, produksi).
Misalnya, katakanlah bahwa Prosesor PayPal memiliki 3 objek dependen, dan masing-masing dependensi tersebut memiliki dua objek lagi. Dan semua objek tersebut harus menarik properti dari konfigurasi. Kode apa adanya akan memikul tanggung jawab untuk membangun semua itu, mengatur properti dari konfigurasi, dll. - semua masalah yang akan ditangani oleh kerangka kerja DI.
Pada awalnya mungkin tidak terlihat jelas apa yang Anda lindungi dengan menggunakan kerangka kerja DI, tetapi hal itu bertambah dan menjadi sangat jelas dari waktu ke waktu. (lol Saya berbicara dari pengalaman setelah mencoba melakukannya dengan cara yang sulit)
...
Dalam prakteknya, bahkan untuk program yang sangat kecil, saya menemukan saya akhirnya menulis dengan gaya DI, dan memecah kelas menjadi pasangan implementasi / pabrik. Artinya, jika saya tidak menggunakan kerangka kerja DI seperti Spring, saya hanya mengumpulkan beberapa kelas pabrik sederhana.
Itu memberikan pemisahan keprihatinan sehingga kelas saya bisa melakukan hal itu, dan kelas pabrik mengambil tanggung jawab untuk membangun & mengonfigurasi hal-hal.
Bukan pendekatan yang diperlukan, tetapi FWIW
...
Secara umum, pola DI / antarmuka mengurangi kompleksitas kode Anda dengan melakukan dua hal:
abstrak dependensi hilir menjadi antarmuka
"Mengangkat" dependensi hulu dari kode Anda dan masuk ke semacam wadah
Selain itu, karena instantiasi dan konfigurasi objek adalah tugas yang cukup akrab, kerangka kerja DI dapat mencapai banyak skala ekonomis melalui notasi standar & menggunakan trik seperti refleksi. Menyebarkan kekhawatiran yang sama di sekitar kelas akhirnya menambah lebih banyak kekacauan daripada yang dipikirkan orang.
sumber
Dengan mengikat grafik objek Anda di awal proses, yaitu, menghubungkannya ke dalam kode, Anda memerlukan kontrak dan implementasinya untuk hadir. Jika orang lain (mungkin bahkan Anda) ingin menggunakan kode itu untuk tujuan yang sedikit berbeda, mereka harus menghitung ulang seluruh grafik objek dan mengimplementasikannya kembali.
Kerangka DI memungkinkan Anda untuk mengambil banyak komponen dan menyatukannya saat runtime. Ini membuat sistem "modular", terdiri dari sejumlah modul yang berfungsi untuk antarmuka satu sama lain, bukan untuk implementasi satu sama lain.
sumber
Pendekatan "instantiate my own ownaborators" mungkin bekerja untuk pohon dependensi , tetapi tentu saja tidak akan berfungsi dengan baik untuk grafik dependensi yang merupakan grafik acyclic diarahkan umum (DAG). Dalam DAG dependensi, banyak node dapat menunjuk ke node yang sama - yang berarti bahwa dua objek menggunakan objek yang sama sebagai kolaborator. Kasus ini sebenarnya tidak dapat dibangun dengan pendekatan yang dijelaskan dalam pertanyaan.
Jika beberapa kolaborator saya (atau kolaborator kolaborator) harus berbagi objek tertentu, saya perlu instantiate objek ini dan meneruskannya ke kolaborator saya. Jadi saya sebenarnya perlu tahu lebih banyak dari kolaborator langsung saya, dan ini jelas tidak berskala.
sumber
UnitOfWork
yang memilikiDbContext
repositori tunggal dan ganda. Semua repositori ini harus menggunakanDbContext
objek yang sama . Dengan usulan "instantiasi diri" OP, itu menjadi tidak mungkin dilakukan.Saya belum pernah menggunakan Google Guice, tetapi saya telah mengambil banyak waktu untuk memigrasikan aplikasi lama N-tier lama di .Net ke arsitektur IoC seperti Onion Layer yang bergantung pada Ketergantungan Injeksi untuk memisahkan hal-hal.
Mengapa Injeksi Ketergantungan?
Tujuan dari Ketergantungan Injeksi sebenarnya bukan untuk testability, itu sebenarnya untuk mengambil aplikasi yang erat dan melonggarkan kopling sebanyak mungkin. (Yang diinginkan oleh produk membuat kode Anda jauh lebih mudah diadaptasi untuk pengujian unit yang tepat)
Mengapa saya harus khawatir tentang kopling sama sekali?
Kopling atau ketergantungan yang ketat bisa menjadi hal yang sangat berbahaya. (Terutama dalam bahasa yang dikompilasi) Dalam kasus ini Anda dapat memiliki perpustakaan, dll, dll yang sangat jarang digunakan yang memiliki masalah yang secara efektif membuat seluruh aplikasi offline. (Seluruh aplikasi Anda mati karena bagian yang tidak penting memiliki masalah ... ini buruk ... BENAR-BENAR buruk) Sekarang ketika Anda memisahkan hal-hal yang sebenarnya Anda dapat mengatur aplikasi Anda sehingga dapat berjalan bahkan jika DLL atau Perpustakaan itu hilang sama sekali! Yakin bahwa satu bagian yang membutuhkan pustaka atau DLL itu tidak akan berfungsi, tetapi sisa dari chugs aplikasi bisa bahagia.
Mengapa saya perlu Injeksi Ketergantungan untuk pengujian yang tepat
Benar-benar Anda hanya ingin kode yang digabungkan secara longgar, Injeksi Ketergantungan hanya memungkinkan itu. Anda dapat secara bebas memasangkan beberapa hal tanpa IoC, tetapi biasanya lebih banyak pekerjaan dan kurang mudah beradaptasi (saya yakin seseorang di luar sana memiliki pengecualian)
Dalam kasus yang Anda berikan, saya pikir akan jauh lebih mudah untuk hanya mengatur injeksi dependensi sehingga saya dapat mengejek kode saya tidak tertarik untuk menghitung sebagai bagian dari tes ini. Cukup beri tahu Anda metode "Hei, saya tahu saya sudah bilang untuk menelepon repositori, tetapi sebaliknya inilah datanya" seharusnya "mengembalikan kedipan " sekarang karena data itu tidak pernah berubah, Anda tahu Anda hanya menguji bagian yang menggunakan data itu, bukan pengambilan aktual data.
Pada dasarnya ketika menguji Anda ingin Integrasi (fungsionalitas) Pengujian yang menguji sepotong fungsionalitas dari awal hingga selesai, dan pengujian unit lengkap yang menguji setiap bagian kode (biasanya pada tingkat metode atau fungsi) secara independen.
Idenya adalah Anda ingin memastikan seluruh fungsionalitas berfungsi, jika tidak Anda ingin tahu persis bagian kode yang tidak berfungsi.
Ini BISA dilakukan tanpa Injeksi Ketergantungan, tetapi biasanya seiring proyek Anda tumbuh, menjadi semakin sulit untuk melakukannya tanpa Injeksi Ketergantungan. (SELALU berasumsi bahwa proyek Anda akan tumbuh! Lebih baik memiliki praktik keterampilan bermanfaat yang tidak perlu daripada menemukan proyek yang meningkat cepat dan membutuhkan refactoring dan rekayasa ulang yang serius setelah semuanya sudah berjalan.)
sumber
Seperti yang saya sebutkan dalam jawaban lain , masalah di sini adalah bahwa Anda ingin kelas
A
bergantung pada beberapa kelasB
tanpa hard-coding kelas manaB
yang digunakan ke dalam kode sumber A. Ini tidak mungkin di Jawa dan C # karena satu-satunya cara untuk mengimpor kelas adalah merujuknya dengan nama yang unik secara global.Dengan menggunakan antarmuka, Anda dapat mengatasi dependensi kelas yang dikodekan dengan keras, tetapi Anda masih harus menggunakan instance antarmuka, dan Anda tidak dapat memanggil konstruktor atau Anda langsung kembali ke kotak 1. Jadi sekarang kode yang sebaliknya dapat menciptakan ketergantungannya mendorong tanggung jawab itu kepada orang lain. Dan ketergantungannya melakukan hal yang sama. Jadi sekarang setiap kali Anda membutuhkan instance kelas Anda akhirnya membangun seluruh pohon dependensi secara manual, sedangkan dalam kasus di mana kelas A bergantung pada B secara langsung, Anda bisa memanggil
new A()
dan memanggil konstruktor itunew B()
, dan seterusnya.Kerangka kerja injeksi ketergantungan mencoba untuk mengatasinya dengan membiarkan Anda menentukan pemetaan antara kelas dan membangun pohon ketergantungan untuk Anda. Tangkapannya adalah ketika Anda mengacaukan pemetaan, Anda akan mengetahui saat runtime, bukan pada waktu kompilasi seperti yang Anda lakukan dalam bahasa yang mendukung modul pemetaan sebagai konsep kelas satu.
sumber
Saya pikir ini adalah kesalahpahaman besar di sini.
Guice adalah kerangka kerja injeksi ketergantungan . Itu membuat DI otomatis . Poin yang mereka buat dalam kutipan yang Anda kutip adalah tentang Guice dapat menghilangkan kebutuhan untuk menciptakan "konstruktor yang dapat diuji" secara manual yang Anda sajikan dalam contoh Anda. Sama sekali tidak ada hubungannya dengan injeksi ketergantungan itu sendiri.
Konstruktor ini:
sudah menggunakan injeksi ketergantungan. Anda pada dasarnya hanya mengatakan bahwa menggunakan DI itu mudah.
Masalah yang dipecahkan oleh Guice adalah bahwa untuk menggunakan konstruktor itu, Anda sekarang harus memiliki kode konstruktor grafik objek di suatu tempat , secara manual melewati objek yang sudah dipakai sebagai argumen konstruktor itu. Guice memungkinkan Anda untuk memiliki satu tempat di mana Anda dapat mengonfigurasi kelas implementasi nyata apa yang sesuai dengan mereka
CreditCardProcessor
danTransactionLog
antarmuka. Setelah konfigurasi itu, setiap kali Anda membuatBillingService
menggunakan Guice, kelas-kelas itu akan diteruskan ke konstruktor secara otomatis.Inilah yang dilakukan oleh kerangka kerja injeksi ketergantungan . Tetapi konstruktor itu sendiri yang Anda sajikan sudah merupakan implementasi dari prinsip injeksi ketergantungan . Kontainer IoC dan kerangka kerja DI adalah sarana untuk mengotomatiskan prinsip-prinsip yang sesuai tetapi tidak ada yang menghentikan Anda untuk melakukan semuanya dengan tangan, itulah intinya.
sumber