Saya telah menggunakan berbagai wadah IoC (Castle.Windsor, Autofac, MEF, dll) untuk. Net di sejumlah proyek. Saya menemukan mereka cenderung sering disalahgunakan dan mendorong sejumlah praktik buruk.
Apakah ada praktik yang telah ditetapkan untuk penggunaan wadah IoC, khususnya ketika menyediakan platform / kerangka kerja? Tujuan saya sebagai penulis kerangka kerja adalah membuat kode sesederhana dan semudah mungkin digunakan. Saya lebih suka menulis satu baris kode untuk membangun objek daripada sepuluh atau bahkan hanya dua.
Misalnya, beberapa kode berbau yang saya perhatikan dan tidak memiliki saran bagus untuk:
Sejumlah besar parameter (> 5) untuk konstruktor. Menciptakan layanan cenderung kompleks; semua dependensi disuntikkan melalui konstruktor - terlepas dari kenyataan bahwa komponen jarang opsional (kecuali mungkin dalam pengujian).
Kurangnya kelas privat dan internal; yang ini mungkin merupakan batasan khusus untuk menggunakan C # dan Silverlight, tapi saya tertarik pada bagaimana ini diselesaikan. Sulit untuk mengatakan apa antarmuka kerangka kerja jika semua kelas bersifat publik; itu memungkinkan saya akses ke bagian pribadi yang mungkin tidak boleh saya sentuh.
Menggabungkan siklus hidup objek ke wadah IoC. Seringkali sulit untuk secara manual membangun dependensi yang diperlukan untuk membuat objek. Siklus hidup objek terlalu sering dikelola oleh kerangka kerja IoC. Saya telah melihat proyek di mana sebagian besar kelas terdaftar sebagai Singletons. Anda mendapatkan kurangnya kontrol eksplisit dan juga dipaksa untuk mengelola internal (berkaitan dengan poin di atas, semua kelas bersifat publik dan Anda harus menyuntikkan mereka).
Sebagai contoh, .Net framework memiliki banyak metode statis. seperti, DateTime.UtcNow. Banyak kali saya melihat ini dibungkus dan disuntikkan sebagai parameter konstruksi.
Bergantung pada implementasi konkret membuat kode saya sulit untuk diuji. Menyuntikkan ketergantungan membuat kode saya sulit digunakan - terutama jika kelas memiliki banyak parameter.
Bagaimana cara saya menyediakan antarmuka yang dapat diuji, serta antarmuka yang mudah digunakan? Apa praktik terbaik?
Jawaban:
Satu-satunya anti-pola ketergantungan injeksi sah yang saya ketahui adalah pola Service Locator , yang merupakan anti-pola ketika kerangka DI digunakan untuk itu.
Semua yang disebut DI anti-pola yang pernah saya dengar, di sini atau di tempat lain, hanya sedikit lebih spesifik dari kasus anti-pola desain OO / perangkat lunak umum. Contohnya:
Konstruktor over-injeksi merupakan pelanggaran terhadap Prinsip Tanggung Jawab Tunggal . Terlalu banyak argumen konstruktor menunjukkan terlalu banyak ketergantungan; terlalu banyak dependensi menunjukkan bahwa kelas berusaha melakukan terlalu banyak. Biasanya kesalahan ini berkorelasi dengan bau kode lain, seperti nama kelas yang panjangnya tidak pasti atau ambigu ("manajer"). Alat analisis statis dapat dengan mudah mendeteksi kopling aferen / eferen yang berlebihan.
Injeksi data, sebagai lawan dari perilaku, adalah subtipe dari poltergeist anti-pattern, dengan geist dalam hal ini adalah wadah. Jika suatu kelas perlu mengetahui tanggal dan waktu saat ini, Anda tidak menyuntikkan
DateTime
, yang merupakan data; sebagai gantinya, Anda menyuntikkan abstraksi pada jam sistem (saya biasanya menyebut milik sayaISystemClock
, meskipun saya pikir ada yang lebih umum dalam proyek SystemWrappers ). Ini tidak hanya benar untuk DI; itu sangat penting untuk testabilitas, sehingga Anda dapat menguji fungsi yang bervariasi waktu tanpa harus benar-benar menunggu mereka.Mendeklarasikan setiap siklus hidup sebagai Singleton, bagi saya, adalah contoh sempurna dari pemrograman pemujaan kargo dan pada tingkat yang lebih rendah disebut " objek pembuangan limbah " yang secara bahasa sehari-hari disebut . Saya telah melihat lebih banyak penyalahgunaan tunggal daripada yang saya ingat, dan sangat sedikit yang melibatkan DI.
Kesalahan umum lainnya adalah tipe antarmuka khusus implementasi (dengan nama yang aneh seperti
IOracleRepository
) dilakukan hanya untuk dapat mendaftarkannya dalam wadah. Ini dengan sendirinya merupakan pelanggaran terhadap Prinsip Ketergantungan Inversi (hanya karena itu merupakan antarmuka, tidak berarti itu benar-benar abstrak) dan sering juga termasuk antarmuka mengasapi yang melanggar Prinsip Segregasi Antarmuka .Kesalahan terakhir yang biasanya saya lihat adalah "ketergantungan opsional", yang mereka lakukan di NerdDinner . Dengan kata lain, ada sebuah konstruktor yang menerima injeksi ketergantungan, tetapi juga yang lain konstruktor yang menggunakan "default" implementasi. Ini juga melanggar DIP dan cenderung mengarah pada pelanggaran LSP juga, sebagai pengembang, seiring waktu, mulai membuat asumsi di sekitar implementasi default, dan / atau memulai instance yang baru menggunakan konstruktor default.
Seperti pepatah lama, Anda dapat menulis FORTRAN dalam bahasa apa pun . Dependency Injection bukan peluru perak yang akan mencegah pengembang dari mengacaukan manajemen ketergantungan mereka, tetapi tidak mencegah sejumlah kesalahan umum / anti-pola:
...dan seterusnya.
Jelas Anda tidak ingin merancang kerangka kerja untuk bergantung pada implementasi wadah IoC tertentu , seperti Unity atau AutoFac. Itu, sekali lagi, melanggar DIP. Tetapi jika Anda menemukan diri Anda berpikir untuk melakukan sesuatu seperti itu, maka Anda pasti telah membuat beberapa kesalahan desain, karena Dependency Injection adalah teknik manajemen ketergantungan tujuan umum dan tidak terikat dengan konsep wadah IoC.
Apa pun dapat membangun pohon ketergantungan; mungkin itu wadah IoC, mungkin itu adalah unit test dengan sekelompok ejekan, mungkin itu adalah driver tes yang memasok data dummy. Kerangka kerja Anda seharusnya tidak peduli, dan sebagian besar kerangka kerja yang saya lihat tidak peduli, tetapi mereka masih menggunakan injeksi ketergantungan secara berat sehingga dapat dengan mudah diintegrasikan ke dalam wadah pilihan pengguna akhir IoC.
DI bukan ilmu roket. Cobalah untuk menghindari
new
danstatic
kecuali ketika ada alasan kuat untuk menggunakannya, seperti metode utilitas yang tidak memiliki dependensi eksternal, atau kelas utilitas yang tidak mungkin memiliki tujuan di luar kerangka kerja (pembungkus interop dan kunci kamus adalah contoh umum dari ini).Banyak masalah dengan kerangka kerja IoC muncul ketika pengembang pertama kali belajar bagaimana menggunakannya, dan bukannya benar-benar mengubah cara mereka menangani dependensi dan abstraksi agar sesuai dengan model IoC, alih-alih mencoba untuk memanipulasi wadah IoC untuk memenuhi harapan mereka. gaya pengkodean lama, yang sering melibatkan kopling tinggi dan kohesi rendah. Kode buruk adalah kode yang buruk, apakah menggunakan teknik DI atau tidak.
sumber