Dependency Injection / IoC praktik wadah saat menulis kerangka kerja

18

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:

  1. 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).

  2. 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.

  3. 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?

Dave Hillier
sumber
1
Praktik buruk apa yang mereka dorong? Untuk kelas privat, Anda tidak perlu memiliki banyak dari mereka jika memiliki enkapsulasi yang baik; untuk satu hal, mereka jauh lebih sulit untuk diuji.
Aaronaught
Kesalahan, 1 dan 2 adalah praktik buruk. Konstruktor besar dan tidak menggunakan enkapsulasi untuk membuat antarmuka minimal? Juga, saya katakan pribadi dan internal (menggunakan internal terlihat untuk menguji). Saya tidak pernah menggunakan kerangka kerja yang memaksa saya untuk menggunakan wadah IoC tertentu.
Dave Hillier
2
Apakah mungkin bahwa tanda yang membutuhkan banyak parameter untuk konstruktor kelas Anda adalah bau kode dan DIP hanya membuat ini lebih jelas?
dreza
3
Menggunakan wadah IoC tertentu dalam suatu kerangka kerja umumnya bukan praktik yang baik. Menggunakan injeksi ketergantungan adalah praktik yang baik. Apakah Anda menyadari perbedaannya?
Aaronaught
1
@Aaronaught (kembali dari kematian). Menggunakan, "injeksi ketergantungan adalah praktik yang baik" adalah salah. Dependency Injection adalah alat yang harus digunakan hanya jika sesuai. Menggunakan Injeksi Ketergantungan sepanjang waktu adalah praktik yang buruk.
Dave Hillier

Jawaban:

27

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 saya ISystemClock, 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 newdan statickecuali 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.

Aaronaught
sumber
Saya punya beberapa pertanyaan. Untuk perpustakaan yang didistribusikan di NuGet, saya tidak bisa bergantung pada sistem IoC seperti yang dilakukan oleh aplikasi menggunakan perpustakaan, bukan oleh perpustakaan itu sendiri. Dalam banyak kasus, pengguna perpustakaan benar-benar tidak peduli dengan ketergantungan internalnya sama sekali. Apakah ada masalah dengan konstruktor default yang memulai dependensi internal, dan konstruktor yang lebih lama untuk pengujian unit dan / atau implementasi yang disesuaikan? Anda juga mengatakan untuk menghindari "baru". Apakah ini berlaku untuk hal-hal seperti StringBuilder, DateTime dan TimeSpan?
Etienne Charland