Sejak belajar (dan mencintai) pengujian otomatis saya menemukan diri saya menggunakan pola injeksi ketergantungan di hampir setiap proyek. Apakah selalu tepat untuk menggunakan pola ini ketika bekerja dengan pengujian otomatis? Apakah ada situasi yang harus Anda hindari menggunakan injeksi ketergantungan?
design-patterns
dependency-injection
Tom Squires
sumber
sumber
Jawaban:
Pada dasarnya, injeksi ketergantungan membuat beberapa (biasanya tetapi tidak selalu valid) asumsi tentang sifat objek Anda. Jika itu salah, DI mungkin bukan solusi terbaik:
Pertama, pada dasarnya, DI mengasumsikan bahwa kopling ketat dari implementasi objek SELALU buruk . Ini adalah inti dari Prinsip Ketergantungan Pembalikan: "ketergantungan tidak boleh dibuat pada konkret; hanya pada abstraksi".
Ini menutup objek dependen untuk berubah berdasarkan perubahan pada implementasi konkret; sebuah kelas tergantung pada ConsoleWriter secara khusus akan perlu berubah jika output perlu pergi ke file sebagai gantinya, tetapi jika kelas hanya bergantung pada IWriter yang mengekspos metode Write (), kita dapat mengganti ConsoleWriter yang saat ini digunakan dengan FileWriter dan kelas dependen tidak akan tahu bedanya (Prinsip Pergantian Liskhov).
Namun, suatu desain TIDAK PERNAH bisa ditutup untuk semua jenis perubahan; jika desain antarmuka IWriter itu sendiri berubah, untuk menambahkan parameter ke Write (), objek kode tambahan (antarmuka IWriter) sekarang harus diubah, di atas objek / metode implementasi dan penggunaannya. Jika perubahan dalam antarmuka yang sebenarnya lebih mungkin daripada perubahan pada implementasi antarmuka tersebut, kopling longgar (dan ketergantungan DI-ing longgar-digabungkan) dapat menyebabkan lebih banyak masalah daripada memecahkannya.
Kedua, dan wajar, DI mengasumsikan bahwa kelas dependen TIDAK PERNAH merupakan tempat yang baik untuk menciptakan ketergantungan . Ini berlaku untuk Prinsip Tanggung Jawab Tunggal; jika Anda memiliki kode yang menciptakan dependensi dan juga menggunakannya, maka ada dua alasan kelas dependen mungkin harus berubah (perubahan penggunaan ATAU implementasi), melanggar SRP.
Namun, sekali lagi, menambahkan lapisan tipuan untuk DI dapat menjadi solusi untuk masalah yang tidak ada; jika logis untuk merangkum logika dalam dependensi, tetapi logika itu adalah satu-satunya implementasi dari dependensi, maka lebih menyakitkan untuk mengkodekan resolusi dependensi yang digabungkan secara longgar (injeksi, lokasi layanan, pabrik) daripada yang seharusnya. hanya menggunakan
new
dan melupakannya.Terakhir, DI pada dasarnya memusatkan pengetahuan tentang semua dependensi DAN implementasinya . Ini meningkatkan jumlah referensi yang harus dimiliki oleh majelis yang melakukan injeksi, dan dalam kebanyakan kasus TIDAK mengurangi jumlah referensi yang diperlukan oleh majelis kelas dependen yang sebenarnya.
SESUATU, DI TEMPAT LAIN, harus memiliki pengetahuan tentang ketergantungan, antarmuka ketergantungan, dan implementasi ketergantungan untuk "menghubungkan titik-titik" dan memenuhi ketergantungan itu. DI cenderung menempatkan semua pengetahuan itu pada tingkat yang sangat tinggi, baik dalam wadah IoC, atau dalam kode yang menciptakan objek "utama" seperti bentuk utama atau Pengendali yang harus menghidrasi (atau menyediakan metode pabrik untuk) dependensi. Ini dapat menempatkan banyak kode yang harus dipasangkan dengan ketat dan banyak referensi perakitan di tingkat tinggi aplikasi Anda, yang hanya membutuhkan pengetahuan ini untuk "menyembunyikan" dari kelas dependen yang sebenarnya (yang dari perspektif yang sangat mendasar adalah tempat terbaik untuk memiliki pengetahuan ini; di mana itu digunakan).
Itu juga biasanya tidak menghapus referensi kata dari bawah kode; dependen masih harus merujuk pustaka yang berisi antarmuka untuk ketergantungannya, yang ada di salah satu dari tiga tempat:
Semua ini, sekali lagi untuk menyelesaikan masalah di tempat-tempat di mana mungkin tidak ada.
sumber
Di luar kerangka kerja dependensi-injeksi, injeksi dependensi (melalui injeksi konstruktor atau injeksi setter) hampir merupakan permainan zero-sum: Anda mengurangi sambungan antara objek A dan dependensi B itu, tetapi sekarang semua objek yang membutuhkan instance A sekarang harus juga membangun objek B.
Anda telah sedikit mengurangi kopling antara A dan B, tetapi mengurangi enkapsulasi A, dan meningkatkan kopling antara A dan setiap kelas yang harus membuat turunan A, dengan menyambungkannya ke dependensi A juga.
Jadi injeksi ketergantungan (tanpa kerangka kerja) adalah sama-sama berbahaya karena sangat membantu.
Biaya tambahan seringkali mudah dibenarkan, namun: jika kode klien lebih tahu tentang bagaimana membangun ketergantungan daripada objek itu sendiri, maka injeksi ketergantungan benar-benar mengurangi kopling; misalnya, Pemindai tidak tahu banyak tentang cara mendapatkan atau membangun aliran input untuk mem-parsing input, atau sumber dari mana kode klien ingin mem-parsing input, jadi injeksi konstruktor dari aliran input adalah solusi yang jelas.
Pengujian adalah pembenaran lain, agar dapat menggunakan dependensi tiruan. Itu harus berarti menambahkan konstruktor tambahan yang digunakan hanya untuk pengujian yang memungkinkan dependensi disuntikkan: jika Anda mengganti konstruktor Anda untuk selalu memerlukan dependensi yang akan disuntikkan, tiba-tiba, Anda harus tahu tentang dependensi dependensi Anda untuk membangun Anda ketergantungan langsung, dan Anda tidak dapat menyelesaikan pekerjaan.
Ini bisa membantu, tetapi Anda harus bertanya pada diri sendiri untuk setiap ketergantungan, apakah manfaat pengujian sebanding dengan biayanya, dan apakah saya benar-benar ingin mengejek ketergantungan ini saat pengujian?
Ketika kerangka kerja dependensi-injeksi ditambahkan, dan konstruksi dependensi didelegasikan bukan ke kode klien tetapi sebaliknya ke kerangka kerja, analisis biaya / manfaat sangat berubah.
Dalam kerangka kerja ketergantungan-injeksi, pengorbanannya sedikit berbeda; apa yang Anda kehilangan dengan menyuntikkan dependensi adalah kemampuan untuk mengetahui dengan mudah implementasi apa yang Anda andalkan, dan mengalihkan tanggung jawab untuk memutuskan dependensi apa yang Anda andalkan untuk beberapa proses resolusi otomatis (misalnya jika kami memerlukan @ Inject'ed Foo , harus ada sesuatu yang menyediakan @Foo, dan yang dependensinya disuntikkan tersedia), atau ke beberapa file konfigurasi tingkat tinggi yang menentukan penyedia apa yang harus digunakan untuk setiap sumber daya, atau untuk beberapa hibrida dari keduanya (misalnya, mungkin ada menjadi proses resolusi otomatis untuk dependensi yang dapat diganti, jika perlu, menggunakan file konfigurasi).
Seperti dalam injeksi konstruktor, saya pikir keuntungan dari melakukannya berakhir, sekali lagi, sangat mirip dengan biaya melakukannya: Anda tidak perlu tahu siapa yang menyediakan data yang Anda andalkan, dan, jika ada banyak potensi penyedia, Anda tidak perlu tahu urutan pilihan untuk memeriksa penyedia di, pastikan bahwa setiap lokasi yang membutuhkan data memeriksa semua penyedia potensial, dll., karena semua itu ditangani pada tingkat tinggi oleh injeksi ketergantungan peron.
Meskipun saya secara pribadi tidak memiliki banyak pengalaman dengan kerangka kerja DI, kesan saya adalah mereka memberikan lebih banyak manfaat daripada biaya ketika sakit kepala menemukan penyedia data atau layanan yang Anda butuhkan memiliki biaya lebih tinggi daripada sakit kepala, ketika sesuatu gagal, tidak segera mengetahui secara lokal kode apa yang menyediakan data buruk yang kemudian menyebabkan kegagalan pada kode Anda.
Dalam beberapa kasus, pola-pola lain yang mengaburkan ketergantungan (misalnya pencari layanan) telah diadopsi (dan mungkin juga terbukti nilainya) ketika kerangka kerja DI muncul di tempat kejadian, dan kerangka kerja DI diadopsi karena mereka menawarkan beberapa keunggulan kompetitif, seperti membutuhkan kode kurang boilerplate, atau berpotensi melakukan lebih sedikit untuk mengaburkan penyedia ketergantungan ketika menjadi perlu untuk menentukan penyedia apa yang sebenarnya digunakan.
sumber
jika Anda membuat entitas basis data, Anda lebih baik memiliki beberapa kelas pabrik yang Anda akan menyuntikkan ke controller Anda,
jika Anda perlu membuat objek primitif seperti int atau panjang. Anda juga harus membuat "dengan tangan" sebagian besar objek perpustakaan standar seperti tanggal, panduan, dll.
jika Anda ingin menyuntikkan string konfigurasi, mungkin ide yang lebih baik untuk menyuntikkan beberapa objek konfigurasi (secara umum disarankan untuk membungkus tipe sederhana menjadi objek yang bermakna: int temperatureInCelsiusDegrees -> CelciusDeegree temperature)
Dan jangan gunakan Service locator sebagai alternatif injeksi ketergantungan, ini anti-pola, info lebih lanjut: http://blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntitiPattern.aspx
sumber
Ketika Anda tidak tahan untuk mendapatkan apa pun dengan membuat proyek Anda dapat dipelihara dan diuji.
Serius, saya suka IoC dan DI secara umum, dan saya akan mengatakan bahwa 98% dari waktu saya akan menggunakan pola itu tanpa gagal. Ini sangat penting dalam lingkungan multi-pengguna, di mana kode Anda dapat digunakan kembali berulang kali oleh anggota tim yang berbeda dan proyek yang berbeda, karena memisahkan logika dari implementasi. Pencatatan adalah contoh utama dari hal ini, antarmuka ILog yang disuntikkan ke dalam kelas seribu kali lebih mudah dikelola daripada hanya dengan memasukkan kerangka kerja logging Anda-du-jour, karena Anda tidak memiliki jaminan proyek lain akan menggunakan kerangka kerja logging yang sama (jika menggunakan satu sama sekali!).
Namun, ada kalanya itu bukan pola yang berlaku. Misalnya, titik masuk fungsional yang diimplementasikan dalam konteks statis oleh penginisialisasi yang tidak dapat ditimpa (WebMethods, saya sedang melihat Anda, tetapi metode Main () Anda di kelas Program adalah contoh lain) tidak bisa hanya memiliki ketergantungan yang disuntikkan pada inisialisasi. waktu. Saya juga mengatakan bahwa prototipe, atau potongan kode investigasi yang dibuang, juga merupakan kandidat yang buruk; manfaat DI adalah manfaat jangka menengah-panjang (testability dan rawatan) yang cukup banyak, jika Anda yakin akan membuang sebagian besar kode dalam waktu seminggu atau lebih, saya akan mengatakan Anda tidak mendapatkan apa-apa dengan mengisolasi dependensi, cukup habiskan waktu yang biasanya Anda habiskan untuk menguji dan mengisolasi dependensi agar kode berfungsi.
Secara keseluruhan, masuk akal untuk mengambil pendekatan pragmatis untuk setiap metodologi atau pola, karena tidak ada yang berlaku 100% dari waktu.
Satu hal yang perlu diperhatikan adalah komentar Anda tentang pengujian otomatis: definisi saya tentang ini adalah tes fungsional otomatis , misalnya tes selenium tertulis jika Anda berada dalam konteks web. Ini umumnya adalah tes kotak hitam sepenuhnya, tanpa perlu tahu tentang cara kerja kode. Jika Anda merujuk pada tes Unit atau Integrasi, saya akan mengatakan bahwa pola DI hampir selalu berlaku untuk proyek apa pun yang sangat bergantung pada jenis pengujian kotak putih, karena, misalnya, memungkinkan Anda untuk menguji hal-hal seperti metode yang menyentuh DB tanpa perlu hadir DB.
sumber
Sementara jawaban lain fokus pada aspek teknis saya ingin menambahkan dimensi praktis.
Selama bertahun-tahun saya sampai pada kesimpulan bahwa ada beberapa persyaratan praktis yang harus dipenuhi jika memperkenalkan Injeksi Ketergantungan adalah untuk menjadi sukses.
Seharusnya ada alasan untuk memperkenalkannya.
Ini kedengarannya jelas, tetapi jika kode Anda hanya mendapatkan hal-hal dari database dan mengembalikannya tanpa logika apa pun, kemudian menambahkan sebuah wadah DI membuat segalanya menjadi lebih kompleks tanpa manfaat nyata. Pengujian integrasi akan lebih penting di sini.
Tim perlu dilatih dan di atas kapal.
Kecuali jika sebagian besar tim ada di papan dan memahami DI menambahkan inversi wadah kontrol menjadi cara lain untuk melakukan sesuatu dan membuat basis kode semakin rumit.
Jika DI diperkenalkan oleh anggota baru tim, karena mereka memahaminya dan menyukainya dan hanya ingin menunjukkan bahwa mereka baik, DAN tim tidak terlibat aktif, ada risiko nyata bahwa DI itu akan benar-benar menurunkan kualitas Kode.
Anda perlu menguji
Sementara decoupling secara umum adalah hal yang baik, DI dapat memindahkan resolusi ketergantungan dari waktu kompilasi ke waktu berjalan. Ini sebenarnya cukup berbahaya jika Anda tidak menguji dengan baik. Jalankan kegagalan resolusi waktu dapat mahal untuk dilacak dan diselesaikan.
(Jelas dari tes Anda bahwa Anda melakukannya, tetapi banyak tim tidak menguji sejauh yang disyaratkan oleh DI.)
sumber
Ini bukan jawaban yang lengkap, tetapi hanya poin lain.
Ketika Anda memiliki aplikasi yang mulai sekali, berjalan untuk waktu yang lama (seperti aplikasi web) DI mungkin baik.
Ketika Anda memiliki aplikasi yang mulai berkali-kali dan berjalan untuk waktu yang lebih singkat (seperti aplikasi seluler), Anda mungkin tidak menginginkan wadah.
sumber
Cobalah untuk menggunakan prinsip-prinsip OOP dasar: gunakan pewarisan untuk mengekstrak fungsionalitas umum, merangkum (menyembunyikan) hal-hal, yang harus dilindungi dari dunia luar menggunakan anggota / tipe pribadi / internal / terlindungi. Gunakan kerangka uji yang kuat untuk menyuntikkan kode hanya untuk tes, misalnya https://www.typemock.com/ atau https://www.telerik.com/products/mocking.aspx .
Kemudian cobalah menulis ulang dengan DI, dan bandingkan kode, yang biasanya Anda lihat dengan DI:
Saya akan mengatakan dari apa yang saya lihat, hampir selalu, kualitas kode sedang menurun dengan DI.
Namun, jika Anda hanya menggunakan pengubah akses "publik" dalam deklarasi kelas, dan / atau pengubah publik / pribadi untuk anggota, dan / atau Anda tidak memiliki pilihan untuk membeli alat uji mahal dan pada saat yang sama Anda memerlukan pengujian unit yang dapat ' t digantikan oleh pengujian integrasional, dan / atau Anda sudah memiliki antarmuka untuk kelas yang Anda pikir untuk menyuntikkan, DI adalah pilihan yang baik!
ps mungkin saya akan mendapatkan banyak minus untuk posting ini, saya percaya karena sebagian besar pengembang modern tidak mengerti bagaimana dan mengapa menggunakan kata kunci internal dan bagaimana mengurangi kopling komponen Anda dan akhirnya mengapa untuk menguranginya) akhirnya, hanya coba kode dan bandingkan
sumber
Sebuah alternatif untuk Dependency Injection menggunakan Layanan Locator . Service Locator lebih mudah dipahami, didebug, dan membuat konstruksi objek lebih mudah terutama jika Anda tidak menggunakan kerangka kerja DI. Service Locators adalah pola yang baik untuk mengelola dependensi statis eksternal , misalnya database yang Anda harus melewati ke setiap objek di lapisan akses data Anda.
Saat refactoring kode lama , seringkali lebih mudah untuk refactor ke Service Locator daripada ke Dependency Injection. Yang Anda lakukan hanyalah mengganti instantiations dengan pencarian layanan dan kemudian memalsukan layanan dalam pengujian unit Anda.
Namun, ada beberapa kerugian pada Service Locator. Mengetahui depandansi kelas lebih sulit karena dependensi tersembunyi dalam implementasi kelas, bukan pada konstruktor atau setter. Dan menciptakan dua objek yang bergantung pada implementasi berbeda dari layanan yang sama sulit atau tidak mungkin.
TLDR : Jika kelas Anda memiliki dependensi statis atau Anda sedang refactoring kode lama, Service Locator bisa dibilang lebih baik daripada DI.
sumber