Saya akan membahas poin-poin Anda secara numerik, tetapi pertama-tama, ada sesuatu yang harus Anda sangat berhati-hati: jangan mengacaukan bagaimana konsumen menggunakan perpustakaan dengan bagaimana perpustakaan diimplementasikan . Contoh bagus dari ini adalah Kerangka Entitas (yang Anda kutip sendiri sebagai perpustakaan yang baik) dan MVC ASP.NET. Keduanya melakukan banyak hal di bawah tenda dengan, misalnya, refleksi, yang sama sekali tidak dianggap desain yang baik jika Anda menyebarkannya melalui kode sehari-hari.
Perpustakaan ini sama sekali tidak "transparan" dalam cara kerjanya, atau apa yang mereka lakukan di belakang layar. Tapi itu bukan kerugian, karena mereka mendukung prinsip pemrograman yang baik di konsumen mereka . Jadi, setiap kali Anda berbicara tentang perpustakaan seperti ini, ingatlah bahwa sebagai konsumen perpustakaan, bukan tugas Anda untuk mengkhawatirkan implementasi atau pemeliharaannya. Anda hanya perlu khawatir tentang bagaimana ini membantu atau menghalangi kode yang Anda tulis yang menggunakan perpustakaan. Jangan mengacaukan konsep-konsep ini!
Jadi, untuk melewati poin:
Segera kami mencapai apa yang hanya bisa saya asumsikan adalah contoh di atas. Anda mengatakan bahwa wadah IOC mengatur sebagian besar dependensi sebagai statis. Yah, mungkin beberapa detail implementasi tentang cara kerjanya termasuk penyimpanan statis (meskipun mengingat bahwa mereka cenderung memiliki instance objek seperti Ninject IKernel
sebagai penyimpanan inti mereka, saya ragu bahkan itu). Tapi ini bukan urusanmu!
Sebenarnya, wadah IoC sama eksplisitnya dengan ruang lingkup seperti injeksi ketergantungan orang miskin. (Aku akan terus membandingkan kontainer IoC dengan DI orang miskin karena membandingkannya dengan tidak ada DI sama sekali tidak adil dan membingungkan. Dan, untuk lebih jelasnya, aku tidak menggunakan "DI orang miskin" sebagai penghinaan.)
Di DI orang miskin, Anda akan instantiate ketergantungan secara manual, lalu menyuntikkannya ke kelas yang membutuhkannya. Pada titik di mana Anda membangun ketergantungan, Anda akan memilih apa yang akan dilakukan dengan itu- menyimpannya dalam variabel cakupan lokal, variabel kelas, variabel statis, tidak menyimpannya sama sekali, apa pun. Anda bisa memberikan contoh yang sama ke banyak kelas, atau Anda bisa membuat yang baru untuk setiap kelas. Masa bodo. Intinya adalah, untuk melihat mana yang terjadi, Anda melihat ke titik - mungkin di dekat root aplikasi - tempat ketergantungan dibuat.
Sekarang bagaimana dengan wadah IOC? Nah, Anda melakukan hal yang persis sama! Sekali lagi, akan dengan terminologi Ninject, Anda melihat di mana mengikat diatur, dan mencari apakah itu mengatakan sesuatu seperti InTransientScope
, InSingletonScope
, atau apa pun. Jika ada hal ini berpotensi lebih jelas, karena Anda memiliki kode di sana yang menyatakan ruang lingkupnya, daripada harus melihat melalui metode untuk melacak apa yang terjadi pada beberapa objek (objek dapat dicakup ke blok, tetapi apakah itu digunakan beberapa kali dalam hal itu blok atau hanya sekali, misalnya). Jadi mungkin Anda ditolak oleh gagasan harus menggunakan fitur pada wadah IoC daripada fitur bahasa mentah untuk menentukan ruang lingkup, tetapi selama Anda mempercayai perpustakaan IoC Anda - yang Anda harus! - tidak ada kerugian nyata untuk itu .
Saya masih tidak benar-benar tahu apa yang Anda bicarakan di sini. Apakah wadah IoC melihat properti pribadi sebagai bagian dari implementasi internal mereka? Saya tidak tahu mengapa mereka melakukannya, tetapi sekali lagi, jika mereka melakukannya, bukan urusan Anda bagaimana perpustakaan yang Anda gunakan diimplementasikan.
Atau, mungkin, mereka mengekspos kemampuan seperti menyuntikkan ke setter pribadi? Jujur saya belum pernah menemukan ini, dan saya ragu apakah ini benar-benar fitur umum. Tetapi bahkan jika itu ada, itu adalah kasus sederhana dari alat yang dapat disalahgunakan. Ingat, bahkan tanpa wadah IoC, hanya beberapa baris kode Refleksi untuk mengakses dan memodifikasi properti pribadi. Ini adalah sesuatu yang hampir tidak boleh Anda lakukan, tetapi itu tidak berarti. NET buruk untuk mengekspos kemampuan. Jika seseorang dengan jelas dan liar menyalahgunakan alat, itu kesalahan orang itu, bukan alat itu.
Poin pamungkas di sini mirip dengan 2. Sebagian besar waktu, kontainer IOC menggunakan konstruktor! Injeksi setter ditawarkan untuk keadaan yang sangat spesifik di mana, karena alasan tertentu, injeksi konstruktor tidak dapat digunakan. Siapa pun yang menggunakan injeksi setter sepanjang waktu untuk menutupi berapa banyak dependensi yang dilewatkan secara masif meninggalkan alat. Itu BUKAN kesalahan alat, itu milik mereka.
Sekarang, jika ini adalah kesalahan yang sangat mudah untuk dibuat dengan tidak bersalah, dan salah satu yang didorong oleh wadah IoC, maka oke, mungkin Anda akan ada benarnya. Itu seperti membuat setiap anggota kelas saya menjadi publik lalu menyalahkan orang lain ketika mereka memodifikasi hal-hal yang seharusnya tidak mereka lakukan, bukan? Tetapi siapa pun yang menggunakan injeksi setter untuk menutupi pelanggaran SRP dengan sengaja mengabaikan atau sama sekali tidak mengetahui prinsip-prinsip desain dasar. Tidak masuk akal untuk menyalahkan ini di wadah IoC.
Itu terutama benar karena itu juga sesuatu yang dapat Anda lakukan dengan mudah dengan DI orang miskin:
var myObject
= new MyTerriblyLargeObject { DependencyA = new Thing(), DependencyB = new Widget(), Dependency C = new Repository(), ... };
Jadi sungguh, kekhawatiran ini tampaknya benar-benar ortogonal apakah wadah IOC digunakan atau tidak.
Mengubah cara kerja kelas Anda bukanlah pelanggaran OCP. Jika ya, maka semua inversi ketergantungan akan mendorong pelanggaran OCP. Jika ini masalahnya, keduanya tidak akan berada dalam akronim SOLID yang sama!
Lebih lanjut, baik poin a) maupun b) hampir tidak ada hubungannya dengan OCP. Saya bahkan tidak tahu bagaimana menjawabnya sehubungan dengan OCP.
Satu-satunya hal yang bisa saya tebak adalah bahwa Anda pikir OCP ada hubungannya dengan perilaku yang tidak diubah saat runtime, atau tentang di mana dalam kode siklus hidup dependensi dikendalikan. Ini bukan. OCP adalah tentang tidak harus mengubah kode yang ada ketika persyaratan ditambahkan atau diubah. Ini semua tentang menulis kode, bukan tentang bagaimana kode yang sudah Anda tulis direkatkan (meskipun, tentu saja, kopling longgar adalah bagian penting untuk mencapai OCP).
Dan satu hal terakhir yang Anda katakan:
Tapi saya tidak bisa menaruh kepercayaan yang sama pada alat pihak ketiga yang mengubah kode kompilasi saya untuk melakukan solusi untuk hal-hal yang saya tidak akan bisa lakukan secara manual.
Ya kamu bisa . Sama sekali tidak ada alasan bagi Anda untuk berpikir bahwa alat-alat ini - yang diandalkan oleh sejumlah besar proyek - lebih buggy atau rentan terhadap perilaku yang tidak terduga daripada perpustakaan pihak ketiga lainnya.
Tambahan
Saya hanya melihat paragraf intro Anda bisa menggunakan beberapa pengalamatan juga. Anda dengan sinis mengatakan bahwa wadah IoC "tidak menggunakan beberapa teknik rahasia yang belum pernah kami dengar" untuk menghindari kode yang berantakan dan rawan duplikasi untuk membuat grafik ketergantungan. Dan Anda benar, apa yang mereka lakukan sebenarnya menangani hal-hal ini dengan teknik dasar yang sama seperti yang selalu dilakukan oleh para programmer.
Biarkan saya berbicara dengan Anda melalui skenario hipotetis. Anda, sebagai programmer, membuat aplikasi besar, dan pada titik masuk, tempat Anda membuat grafik objek, Anda melihat ada kode yang cukup berantakan. Ada beberapa kelas yang digunakan berulang kali, dan setiap kali Anda membangun salah satu dari itu Anda harus membangun seluruh rantai ketergantungan di bawahnya lagi. Selain itu, Anda tidak memiliki cara ekspresif untuk mendeklarasikan atau mengendalikan siklus hidup dependensi, kecuali dengan kode khusus untuk masing-masingnya. Kode Anda tidak terstruktur dan penuh pengulangan. Ini adalah kekacauan yang Anda bicarakan di paragraf intro Anda.
Jadi pertama, Anda mulai sedikit refactor - di mana beberapa kode berulang cukup terstruktur Anda menariknya ke dalam metode pembantu, dan seterusnya. Tapi kemudian Anda mulai berpikir- ini masalah yang saya mungkin bisa mengatasi dalam umum akal, salah satu yang tidak spesifik untuk proyek tertentu tetapi bisa membantu Anda dalam semua proyek masa depan Anda?
Jadi Anda duduk, dan memikirkannya, dan memutuskan bahwa harus ada kelas yang dapat menyelesaikan dependensi. Dan Anda membuat sketsa metode publik apa yang dibutuhkan:
void Bind(Type interfaceType, Type concreteType, bool singleton);
T Resolve<T>();
Bind
mengatakan "di mana Anda melihat argumen konstruktor tipe interfaceType
, berikan contoh concreteType
". singleton
Parameter tambahan mengatakan apakah akan menggunakan instance yang sama concreteType
setiap kali, atau selalu membuat yang baru.
Resolve
hanya akan mencoba untuk membangun T
dengan konstruktor apa pun yang dapat ditemukan argumen siapa semua jenis yang sebelumnya terikat. Itu juga bisa menyebut dirinya secara rekursif untuk menyelesaikan dependensi sepenuhnya. Jika itu tidak dapat menyelesaikan sebuah instance karena tidak semuanya telah diikat, ia mengeluarkan pengecualian.
Anda dapat mencoba menerapkan ini sendiri, dan Anda akan menemukan Anda perlu sedikit refleksi, dan beberapa caching untuk binding mana singleton
benar, tetapi tentu saja tidak ada yang drastis atau mengerikan. Dan begitu Anda selesai - voila , Anda memiliki inti dari wadah IoC Anda sendiri! Apakah itu benar-benar menakutkan? Satu-satunya perbedaan nyata antara ini dan Ninject atau StructureMap atau Castle Windsor atau apa pun yang Anda inginkan adalah bahwa mereka memiliki lebih banyak fungsi untuk menutupi kasus penggunaan (banyak!) Di mana versi dasar ini tidak akan cukup. Tetapi pada intinya, apa yang Anda miliki adalah esensi dari wadah IoC.
IOC
danExplicitness of code
persis hal yang saya miliki masalah dengan. Manual DI dapat dengan mudah menjadi tugas, tetapi setidaknya menempatkan alur program eksplisit mandiri di satu tempat - "Anda mendapatkan apa yang Anda lihat, Anda melihat apa yang Anda dapatkan". Sementara binding dan deklarasi kontainer IOC dapat dengan mudah menjadi program paralel tersembunyi sendiri.