Jika saya mengerti dengan benar, mekanisme khas untuk Injeksi Ketergantungan adalah menyuntikkan baik melalui konstruktor kelas atau melalui properti umum (anggota) kelas.
Ini memperlihatkan ketergantungan yang disuntikkan dan melanggar prinsip OOP enkapsulasi.
Apakah saya benar dalam mengidentifikasi pengorbanan ini? Bagaimana Anda menangani masalah ini?
Silakan lihat juga jawaban saya untuk pertanyaan saya sendiri di bawah ini.
Jawaban:
Ada cara lain untuk melihat masalah ini yang mungkin menarik bagi Anda.
Saat kami menggunakan injeksi IoC / dependensi, kami tidak menggunakan konsep OOP. Memang kami menggunakan bahasa OO sebagai 'tuan rumah', tetapi ide di balik IoC berasal dari rekayasa perangkat lunak berorientasi komponen, bukan OO.
Perangkat lunak komponen adalah semua tentang mengelola dependensi - contoh yang umum digunakan adalah mekanisme .NET's Assembly. Setiap majelis menerbitkan daftar majelis yang dirujuknya, dan ini membuatnya lebih mudah untuk mengumpulkan (dan memvalidasi) bagian-bagian yang diperlukan untuk aplikasi yang berjalan.
Dengan menerapkan teknik serupa di program OO kami melalui IoC, kami bertujuan untuk membuat program lebih mudah untuk dikonfigurasi dan dipelihara. Menerbitkan dependensi (sebagai parameter konstruktor atau apa pun) adalah bagian penting dari ini. Enkapsulasi tidak benar-benar berlaku, karena di dunia berorientasi komponen / layanan, tidak ada 'tipe implementasi' untuk rincian dari mana bocor.
Sayangnya bahasa kami saat ini tidak memisahkan konsep berorientasi objek halus dari konsep berorientasi komponen kasar, jadi ini adalah perbedaan yang harus Anda pegang hanya dalam pikiran Anda :)
sumber
Ini pertanyaan yang bagus - tetapi pada beberapa titik, enkapsulasi dalam bentuk paling murni perlu dilanggar jika objek tersebut pernah memiliki ketergantungannya terpenuhi. Beberapa penyedia dependensi harus mengetahui bahwa objek tersebut memerlukan
Foo
, dan penyedia harus memiliki cara untuk menyediakanFoo
objek.Klasik kasus terakhir ini ditangani seperti yang Anda katakan, melalui argumen konstruktor atau metode penyetel. Namun, ini tidak selalu benar - saya tahu bahwa versi terbaru dari kerangka kerja DI DI di Jawa, misalnya, memungkinkan Anda membuat anotasi bidang pribadi (misalnya dengan
@Autowired
) dan dependensi akan ditetapkan melalui refleksi tanpa Anda perlu mengekspos dependensi melalui salah satu metode / konstruktor kelas publik. Ini mungkin jenis solusi yang Anda cari.Yang mengatakan, saya tidak berpikir injeksi konstruktor banyak masalah, baik. Saya selalu merasa bahwa objek harus sepenuhnya valid setelah konstruksi, sehingga apa pun yang mereka butuhkan untuk melakukan perannya (yaitu dalam keadaan valid) harus disediakan melalui konstruktor. Jika Anda memiliki objek yang memerlukan kolaborator untuk bekerja, tampaknya baik bagi saya bahwa konstruktor secara publik mengiklankan persyaratan ini dan memastikannya terpenuhi ketika instance kelas baru dibuat.
Idealnya ketika berhadapan dengan objek, Anda tetap berinteraksi dengan mereka melalui antarmuka, dan semakin banyak Anda melakukan ini (dan memiliki ketergantungan kabel melalui DI), semakin sedikit Anda benar-benar harus berurusan dengan konstruktor sendiri. Dalam situasi ideal, kode Anda tidak berurusan dengan atau bahkan membuat instance kelas yang konkret; jadi itu hanya diberikan
IFoo
melalui DI, tanpa khawatir tentang apa yangFooImpl
mengindikasikan konstruktor perlu melakukan tugasnya, dan bahkan tanpa menyadariFooImpl
keberadaannya. Dari sudut pandang ini, enkapsulasi sempurna.Tentu saja ini adalah pendapat, tetapi menurut saya DI tidak selalu melanggar enkapsulasi dan pada kenyataannya dapat membantu dengan memusatkan semua pengetahuan internal yang diperlukan ke satu tempat. Tidak hanya ini hal yang baik dalam dirinya sendiri, tetapi bahkan lebih baik tempat ini di luar basis kode Anda sendiri, jadi tidak ada kode yang Anda tulis perlu tahu tentang dependensi kelas.
sumber
Yah, terus terang saja, semuanya melanggar enkapsulasi. :) Ini semacam prinsip tender yang harus diperlakukan dengan baik.
Jadi, apa yang melanggar enkapsulasi?
Warisan tidak .
Pemrograman berorientasi aspek tidak . Sebagai contoh, Anda mendaftar pada panggilan balikMethodCall () dan itu memberi Anda peluang besar untuk menyuntikkan kode ke evaluasi metode normal, menambahkan efek samping aneh dll.
Deklarasi teman di C ++ tidak .
Perluasan kelas di Ruby tidak . Tetapkan ulang metode string di suatu tempat setelah kelas string sepenuhnya ditentukan.
Nah, banyak hal tidak .
Enkapsulasi adalah prinsip yang baik dan penting. Tapi bukan satu-satunya.
sumber
Ya, DI melanggar enkapsulasi (juga dikenal sebagai "menyembunyikan informasi").
Tetapi masalah sebenarnya muncul ketika pengembang menggunakannya sebagai alasan untuk melanggar prinsip KISS (Keep It Short and Simple) dan YAGNI (You Ain't Gonna Need It).
Secara pribadi, saya lebih suka solusi yang sederhana dan efektif. Saya kebanyakan menggunakan operator "baru" untuk instantiate dependensi stateful kapanpun dan dimanapun mereka diperlukan. Sederhana, dikemas dengan baik, mudah dimengerti, dan mudah diuji. Jadi mengapa tidak?
sumber
Wadah / sistem injeksi ketergantungan yang baik akan memungkinkan injeksi konstruktor. Objek dependen akan dienkapsulasi, dan tidak perlu diekspos secara publik sama sekali. Lebih jauh, dengan menggunakan sistem DP, tidak ada kode Anda yang "tahu" detail tentang bagaimana objek dibangun, bahkan mungkin termasuk objek yang sedang dibangun. Ada lebih banyak enkapsulasi dalam hal ini karena hampir semua kode Anda tidak hanya dilindungi dari pengetahuan tentang objek yang dienkapsulasi, tetapi bahkan tidak berpartisipasi dalam konstruksi objek.
Sekarang, saya berasumsi Anda membandingkan terhadap kasus di mana objek yang dibuat membuat objek yang dienkapsulasi sendiri, kemungkinan besar dalam konstruktornya. Pemahaman saya tentang DP adalah bahwa kita ingin mengambil tanggung jawab ini dari objek dan memberikannya kepada orang lain. Untuk itu, "orang lain", yang merupakan wadah DP dalam kasus ini, memang memiliki pengetahuan intim yang "melanggar" enkapsulasi; manfaatnya adalah ia menarik pengetahuan itu keluar dari objek itu sendiri. Seseorang harus memilikinya. Sisa aplikasi Anda tidak.
Saya akan memikirkannya seperti ini: Wadah injeksi ketergantungan / sistem melanggar enkapsulasi, tetapi kode Anda tidak. Bahkan, kode Anda lebih "dienkapsulasi" dari sebelumnya.
sumber
Seperti yang ditunjukkan Jeff Sternal dalam komentar untuk pertanyaan itu, jawabannya sepenuhnya tergantung pada bagaimana Anda mendefinisikan enkapsulasi .
Tampaknya ada dua kubu utama dari apa arti enkapsulasi:
File
objek mungkin memiliki metode untukSave
,Print
,Display
,ModifyText
, dllKedua definisi ini saling bertentangan secara langsung. Jika suatu
File
objek dapat mencetak dirinya sendiri, itu akan sangat bergantung pada perilaku printer. Di sisi lain, jika ia hanya tahu tentang sesuatu yang dapat dicetak untuk itu (IFilePrinter
antarmuka atau semacam itu), makaFile
objek tidak harus tahu apa-apa tentang pencetakan, dan dengan bekerja dengannya akan mengurangi ketergantungan pada objek.Jadi, injeksi dependensi akan merusak enkapsulasi jika Anda menggunakan definisi pertama. Tapi, terus terang saya tidak tahu apakah saya suka definisi pertama - itu jelas tidak skala (jika ya, MS Word akan menjadi satu kelas besar).
Di sisi lain, injeksi ketergantungan hampir wajib jika Anda menggunakan definisi enkapsulasi kedua.
sumber
Itu tidak melanggar enkapsulasi. Anda menyediakan kolaborator, tetapi kelas memutuskan bagaimana menggunakannya. Selama Anda mengikuti Tell, jangan bertanya hal-hal baik-baik saja. Saya menemukan injeksi konstructer lebih disukai, tetapi setter bisa baik-baik saja asalkan mereka pintar. Itu mereka mengandung logika untuk mempertahankan invarian yang diwakili kelas.
sumber
Ini mirip dengan jawaban terangkat tetapi saya ingin berpikir keras - mungkin orang lain melihat hal-hal seperti ini juga.
OO Klasik menggunakan konstruktor untuk mendefinisikan kontrak "inisialisasi" publik untuk konsumen kelas (menyembunyikan SEMUA detail implementasi; alias enkapsulasi). Kontrak ini dapat memastikan bahwa setelah instantiasi Anda memiliki objek yang siap digunakan (yaitu tidak ada langkah inisialisasi tambahan untuk diingat (er, dilupakan) oleh pengguna).
(konstruktor) DI tidak dapat dipungkiri memecah encapsulation dengan pendarahan implementasi detail melalui antarmuka konstruktor publik ini. Selama kami masih menganggap konstruktor publik yang bertanggung jawab untuk menentukan kontrak inisialisasi untuk pengguna, kami telah menciptakan pelanggaran enkapsulasi yang mengerikan.
Contoh Teoritis:
Class Foo memiliki 4 metode dan membutuhkan integer untuk inisialisasi, sehingga konstruktornya terlihat seperti Foo (ukuran int) dan segera jelas bagi pengguna kelas Foo bahwa mereka harus memberikan ukuran pada instantiasi agar Foo dapat berfungsi.
Katakanlah implementasi khusus Foo ini mungkin juga memerlukan IWidget untuk melakukan tugasnya. Injeksi konstruktor dari dependensi ini meminta kami membuat konstruktor seperti Foo (ukuran int, widget IWidget)
Apa yang mengganggu saya tentang ini adalah sekarang kita memiliki konstruktor yang memadukan data inisialisasi dengan dependensi - satu input menarik bagi pengguna kelas ( ukuran ), yang lain adalah ketergantungan internal yang hanya berfungsi untuk membingungkan pengguna dan merupakan implementasi detail ( widget ).
Parameter ukuran BUKAN ketergantungan - itu sederhana nilai inisialisasi per-instance. IoC bagus untuk dependensi eksternal (seperti widget) tetapi tidak untuk inisialisasi keadaan internal.
Lebih buruk lagi, bagaimana jika Widget hanya diperlukan untuk 2 dari 4 metode pada kelas ini; Saya mungkin mengeluarkan overhead instantiation untuk Widget meskipun mungkin tidak digunakan!
Bagaimana cara kompromi / rekonsiliasi ini?
Salah satu pendekatan adalah beralih secara eksklusif ke antarmuka untuk menentukan kontrak operasi; dan menghapus penggunaan konstruktor oleh pengguna. Agar konsisten, semua objek harus diakses melalui antarmuka saja, dan dipakai hanya melalui beberapa bentuk resolver (seperti wadah IOC / DI). Hanya wadah yang bisa instantiate hal.
Itu menangani ketergantungan Widget, tetapi bagaimana kita menginisialisasi "ukuran" tanpa menggunakan metode inisialisasi terpisah pada antarmuka Foo? Menggunakan solusi ini, kami kehilangan kemampuan untuk memastikan bahwa instance Foo diinisialisasi penuh pada saat Anda mendapatkan instance. Nyebelin, karena saya sangat suka ide dan kesederhanaan injeksi konstruktor.
Bagaimana cara mencapai inisialisasi dijamin di dunia DI ini, ketika inisialisasi LEBIH dari HANYA ketergantungan eksternal?
sumber
Enkapsulasi murni adalah cita-cita yang tidak pernah dapat dicapai. Jika semua dependensi disembunyikan maka Anda tidak akan memerlukan DI sama sekali. Pikirkan seperti ini, jika Anda benar-benar memiliki nilai pribadi yang dapat diinternalisasi dalam objek, katakan misalnya nilai integer dari kecepatan objek mobil, maka Anda tidak memiliki ketergantungan eksternal dan tidak perlu membalikkan atau menyuntikkan ketergantungan itu. Nilai-nilai keadaan internal semacam ini yang dioperasikan murni oleh fungsi-fungsi pribadi adalah apa yang selalu ingin Anda enkapsulasi.
Tetapi jika Anda sedang membangun mobil yang menginginkan jenis mesin tertentu maka Anda memiliki ketergantungan eksternal. Anda dapat membuat instance engine tersebut - misalnya GMOverHeadCamEngine () baru - secara internal di dalam konstruktor objek mobil, menjaga enkapsulasi tetapi membuat kopling yang jauh lebih berbahaya ke kelas beton GMOverHeadCamEngine, atau Anda dapat menyuntikkannya, memungkinkan objek Mobil Anda beroperasi secara agnostik (dan jauh lebih kuat) misalnya antarmuka IEngine tanpa ketergantungan konkret. Apakah Anda menggunakan wadah IOC atau DI sederhana untuk mencapai ini bukan itu intinya - intinya adalah bahwa Anda punya Mobil yang dapat menggunakan berbagai jenis mesin tanpa digabungkan ke salah satu dari mereka, sehingga membuat basis kode Anda lebih fleksibel dan kurang rentan terhadap efek samping.
DI bukan merupakan pelanggaran enkapsulasi, ini adalah cara meminimalkan penggandengan ketika enkapsulasi harus dipecah sebagai hal yang biasa dalam setiap proyek OOP. Menyuntikkan ketergantungan ke antarmuka eksternal meminimalkan efek samping kopling dan memungkinkan kelas Anda untuk tetap agnostik tentang implementasi.
sumber
Itu tergantung pada apakah ketergantungan itu benar-benar detail implementasi atau sesuatu yang ingin / perlu diketahui klien dengan cara tertentu. Satu hal yang relevan adalah tingkat abstraksi yang ditargetkan oleh kelas. Berikut ini beberapa contohnya:
Jika Anda memiliki metode yang menggunakan caching di bawah tenda untuk mempercepat panggilan, maka objek cache harus berupa Singleton atau sesuatu dan tidak boleh disuntikkan. Fakta bahwa cache sedang digunakan sama sekali adalah detail implementasi yang tidak perlu dipedulikan oleh klien kelas Anda.
Jika kelas Anda perlu menampilkan aliran data, mungkin masuk akal untuk menyuntikkan aliran output sehingga kelas dapat dengan mudah menampilkan hasil ke array, file, atau di mana pun orang lain mungkin ingin mengirim data.
Untuk area abu-abu, katakanlah Anda memiliki kelas yang melakukan beberapa simulasi monte carlo. Perlu sumber keacakan. Di satu sisi, fakta bahwa ini membutuhkan ini adalah detail implementasi di mana klien benar-benar tidak peduli dari mana asal keacakan. Di sisi lain, karena generator bilangan acak dunia nyata menghasilkan pertukaran antara tingkat keacakan, kecepatan, dll. Yang mungkin ingin dikendalikan oleh klien, dan klien mungkin ingin mengendalikan penyemaian untuk mendapatkan perilaku berulang, injeksi mungkin masuk akal. Dalam hal ini, saya akan menyarankan menawarkan cara membuat kelas tanpa menentukan generator nomor acak, dan menggunakan Singleton-thread lokal sebagai default. Jika / ketika kebutuhan untuk kontrol yang lebih baik muncul, sediakan konstruktor lain yang memungkinkan sumber keacakan untuk diinjeksi.
sumber
Saya percaya pada kesederhanaan. Menerapkan IOC / Dependecy Injection di kelas Domain tidak membuat perbaikan apa pun kecuali membuat kode lebih sulit untuk dipelihara dengan memiliki file xml eksternal yang menggambarkan relasinya. Banyak teknologi seperti EJB 1.0 / 2.0 & struts 1.1 membalikkan kembali dengan mengurangi hal-hal yang dimasukkan ke dalam XML dan mencoba memasukkannya ke dalam kode sebagai pengumuman dll. Jadi menerapkan IOC untuk semua kelas yang Anda kembangkan akan membuat kode tersebut tidak masuk akal.
IOC memilikinya manfaat ketika objek dependen tidak siap untuk dibuat pada waktu kompilasi. Hal ini dapat terjadi di sebagian besar komponen arsitektur tingkat abstrak infrasture, mencoba membangun kerangka dasar bersama yang mungkin perlu untuk skenario yang berbeda. Di tempat-tempat itu, penggunaan IOC lebih masuk akal. Namun ini tidak membuat kode lebih sederhana / dapat dipelihara.
Seperti semua teknologi lainnya, ini juga memiliki PRO & KONS. Kekhawatiran saya adalah, kami menerapkan teknologi terbaru di semua tempat terlepas dari penggunaan konteks terbaik mereka.
sumber
Enkapsulasi hanya rusak jika kelas memiliki kedua tanggung jawab untuk membuat objek (yang membutuhkan pengetahuan tentang detail implementasi) dan kemudian menggunakan kelas (yang tidak membutuhkan pengetahuan tentang detail ini). Saya akan menjelaskan alasannya, tetapi pertama-tama anaologi mobil cepat:
Tetapi kembali ke pengkodean. Enkapsulasi adalah "menyembunyikan detail implementasi dari sesuatu yang menggunakan implementasi itu." Enkapsulasi adalah hal yang baik karena detail implementasi dapat berubah tanpa diketahui pengguna kelas.
Saat menggunakan injeksi dependensi, injeksi konstruktor digunakan untuk membangun objek tipe layanan (sebagai lawan dari entitas / nilai objek yang menyatakan model). Setiap variabel anggota dalam objek jenis layanan mewakili detail implementasi yang tidak boleh bocor. mis. nomor port soket, kredensial basis data, kelas lain untuk dipanggil untuk melakukan enkripsi, cache, dll.
The konstruktor relevan ketika kelas sedang awalnya dibuat. Ini terjadi selama fase konstruksi saat kontainer DI Anda (atau pabrik) menyatukan semua objek servis. Wadah DI hanya tahu tentang detail implementasi. Ia tahu semua tentang detail implementasi seperti orang-orang di pabrik Kombi tahu tentang busi.
Pada saat run-time, objek layanan yang dibuat disebut apon untuk melakukan beberapa pekerjaan nyata. Pada saat ini, penelepon objek tidak mengetahui detail implementasi.
Sekarang, kembali ke enkapsulasi. Jika detail implementasi berubah, maka kelas yang menggunakan implementasi itu pada saat run-time tidak perlu diubah. Enkapsulasi tidak rusak.
Jika detail implementasi berubah, wadah DI (atau pabrik) perlu diubah. Anda tidak pernah mencoba menyembunyikan detail implementasi dari pabrik sejak awal.
sumber
Setelah berjuang dengan masalah ini sedikit lebih jauh, saya sekarang berpendapat bahwa Injeksi Ketergantungan tidak (pada saat ini) melanggar enkapsulasi sampai tingkat tertentu. Tapi jangan salah paham - saya pikir menggunakan injeksi ketergantungan layak untuk tradeoff dalam kebanyakan kasus.
Kasus mengapa DI melanggar enkapsulasi menjadi jelas ketika komponen yang Anda kerjakan akan dikirim ke pihak "eksternal" (pikirkan menulis perpustakaan untuk pelanggan).
Ketika komponen saya memerlukan sub-komponen yang akan disuntikkan melalui konstruktor (atau properti publik) tidak ada jaminan untuk
Pada saat yang sama tidak dapat dikatakan demikian
Kedua kutipan tersebut berasal dari wikipedia .
Untuk memberikan contoh spesifik: Saya perlu memberikan DLL sisi klien yang menyederhanakan dan menyembunyikan komunikasi ke layanan WCF (pada dasarnya fasad jarak jauh). Karena ini tergantung pada 3 kelas proxy WCF yang berbeda, jika saya mengambil pendekatan DI, saya dipaksa untuk mengeksposnya melalui konstruktor. Dengan itu saya mengekspos internal lapisan komunikasi saya yang saya coba sembunyikan.
Secara umum saya semua untuk DI. Dalam contoh khusus (ekstrem) ini, menurut saya berbahaya.
sumber
DI melanggar Enkapsulasi untuk objek NON-Dibagikan - titik. Objek yang dibagikan memiliki rentang hidup di luar objek yang sedang dibuat, dan karenanya harus AGREGASI ke dalam objek yang sedang dibuat. Objek yang bersifat pribadi untuk objek yang sedang dibuat harus dikomposisikan ke dalam objek yang dibuat - ketika objek yang dibuat dihancurkan, dibutuhkan objek yang disusun dengannya. Mari kita ambil tubuh manusia sebagai contoh. Apa yang dikomposisikan dan apa yang dikumpulkan. Jika kita menggunakan DI, konstruktor tubuh manusia akan memiliki 100 objek. Banyak organ, misalnya, yang berpotensi diganti. Tapi, mereka masih tersusun dalam tubuh. Sel darah dibuat dalam tubuh (dan dihancurkan) setiap hari, tanpa perlu pengaruh eksternal (selain protein). Dengan demikian, sel-sel darah dibuat secara internal oleh tubuh - BloodCell baru ().
Pendukung DI berpendapat bahwa suatu objek TIDAK PERNAH harus menggunakan operator baru. Pendekatan "murni" itu tidak hanya melanggar enkapsulasi tetapi juga Prinsip Substitusi Liskov bagi siapa pun yang menciptakan objek.
sumber
Saya berjuang dengan gagasan ini juga. Pada awalnya, 'persyaratan' untuk menggunakan wadah DI (seperti Spring) untuk instantiate objek terasa seperti melompat melalui lingkaran. Tetapi pada kenyataannya, itu benar-benar bukan sebuah lingkaran - itu hanyalah cara lain yang 'dipublikasikan' untuk membuat objek yang saya butuhkan. Tentu, enkapsulasi 'rusak' karena seseorang 'di luar kelas' tahu apa yang dibutuhkannya, tetapi sebenarnya bukan seluruh sistem yang tahu itu - itu adalah wadah DI. Tidak ada yang ajaib terjadi secara berbeda karena DI 'tahu' satu objek membutuhkan yang lain.
Bahkan itu menjadi lebih baik - dengan berfokus pada Pabrik dan Gudang saya bahkan tidak perlu tahu DI terlibat sama sekali! Itu bagi saya menempatkan tutupnya kembali pada enkapsulasi. Wah!
sumber
PS. Dengan memberikan Injeksi Ketergantungan Anda tidak harus memutus Enkapsulasi . Contoh:
Kode klien masih belum tahu detail implementasi.
sumber
Mungkin ini adalah cara berpikir naif tentang itu, tetapi apa perbedaan antara konstruktor yang mengambil parameter integer dan konstruktor yang menggunakan layanan sebagai parameter? Apakah ini berarti bahwa mendefinisikan bilangan bulat di luar objek baru dan memasukkannya ke dalam objek memecah enkapsulasi? Jika layanan ini hanya digunakan dalam objek baru, saya tidak melihat bagaimana itu akan memecah enkapsulasi.
Juga, dengan menggunakan semacam fitur autowiring (Autofac untuk C #, misalnya), itu membuat kode sangat bersih. Dengan membangun metode ekstensi untuk pembangun Autofac, saya dapat memotong BANYAK kode konfigurasi DI yang harus saya pertahankan dari waktu ke waktu seiring bertambahnya daftar dependensi.
sumber
Saya pikir itu jelas bahwa setidaknya DI secara signifikan melemahkan enkapsulasi. Selain itu, berikut adalah beberapa kelemahan DI lainnya untuk dipertimbangkan.
Itu membuat kode lebih sulit untuk digunakan kembali. Modul yang dapat digunakan klien tanpa harus secara eksplisit menyediakan dependensi, jelas lebih mudah digunakan daripada modul yang entah bagaimana klien harus menemukan apa dependensi komponen itu dan kemudian membuatnya tersedia. Misalnya komponen yang awalnya dibuat untuk digunakan dalam aplikasi ASP mungkin berharap memiliki dependensinya yang disediakan oleh wadah DI yang menyediakan contoh objek dengan masa hidup yang terkait dengan permintaan http klien. Ini mungkin tidak mudah direproduksi di klien lain yang tidak datang dengan wadah DI built in yang sama dengan aplikasi ASP asli.
Itu bisa membuat kode lebih rapuh. Ketergantungan yang diberikan oleh spesifikasi antarmuka dapat diimplementasikan dengan cara yang tidak terduga yang menimbulkan seluruh kelas bug runtime yang tidak mungkin dengan ketergantungan beton yang diselesaikan secara statis.
Itu bisa membuat kode kurang fleksibel dalam arti bahwa Anda mungkin berakhir dengan lebih sedikit pilihan tentang bagaimana Anda ingin itu berfungsi. Tidak setiap kelas perlu memiliki semua dependensinya untuk seumur hidup dari instance yang dimiliki, namun dengan banyak implementasi DI Anda tidak memiliki pilihan lain.
Dengan mengingat hal itu saya pikir pertanyaan yang paling penting kemudian menjadi, " apakah ketergantungan tertentu perlu ditentukan secara eksternal sama sekali? ". Dalam praktiknya, saya jarang merasa perlu untuk membuat ketergantungan yang diberikan secara eksternal hanya untuk mendukung pengujian.
Di mana ketergantungan benar-benar perlu dipasok secara eksternal, yang biasanya menunjukkan bahwa hubungan antara objek adalah kolaborasi daripada ketergantungan internal, dalam hal ini tujuan yang sesuai kemudian enkapsulasi masing - masing kelas, daripada enkapsulasi satu kelas di dalam yang lain .
Dalam pengalaman saya, masalah utama mengenai penggunaan DI adalah apakah Anda memulai dengan kerangka kerja aplikasi dengan built in DI, atau Anda menambahkan dukungan DI ke basis kode Anda, untuk beberapa alasan orang berasumsi bahwa karena Anda memiliki dukungan DI yang harus benar cara untuk instantiate segalanya . Mereka bahkan tidak pernah repot-repot mengajukan pertanyaan "apakah ketergantungan ini perlu ditentukan secara eksternal?". Dan lebih buruk lagi, mereka juga mulai mencoba memaksa orang lain untuk menggunakan dukungan DI untuk semuanya juga.
Hasil dari ini adalah bahwa basis kode Anda mulai berubah menjadi keadaan di mana membuat instance apa pun dari basis kode Anda memerlukan rim konfigurasi wadah DI yang tumpul, dan men-debug sesuatu adalah dua kali lebih sulit karena Anda memiliki beban kerja ekstra untuk mencoba mengidentifikasi bagaimana dan di mana ada sesuatu yang dipakai.
Jadi jawaban saya untuk pertanyaannya adalah ini. Gunakan DI tempat Anda dapat mengidentifikasi masalah aktual yang dipecahkannya untuk Anda, yang tidak bisa Anda selesaikan dengan cara lain apa pun.
sumber
Saya setuju bahwa dengan ekstrem, DI dapat melanggar enkapsulasi. Biasanya DI memperlihatkan dependensi yang tidak pernah benar-benar dienkapsulasi. Berikut adalah contoh sederhana yang dipinjam dari Lajang Miško Hevery adalah Pembohong Patologis :
Anda mulai dengan tes CreditCard dan menulis tes unit sederhana.
Bulan depan Anda mendapatkan tagihan sebesar $ 100. Mengapa Anda ditagih? Tes unit mempengaruhi basis data produksi. Secara internal, panggilan CreditCard
Database.getInstance()
. Refactoring CreditCard sehingga memerlukanDatabaseInterface
konstruktor memperlihatkan fakta bahwa ada ketergantungan. Tetapi saya berpendapat bahwa ketergantungan tidak pernah dienkapsulasi sejak kelas CreditCard menyebabkan efek samping yang terlihat secara eksternal. Jika Anda ingin menguji CreditCard tanpa refactoring, Anda tentu dapat mengamati ketergantungannya.Saya tidak berpikir perlu khawatir apakah mengekspos Database sebagai ketergantungan mengurangi enkapsulasi, karena itu desain yang bagus. Tidak semua keputusan DI akan lurus ke depan. Namun, tidak ada jawaban lain yang menunjukkan contoh balasan.
sumber
CreditCard
berubah dari WebAPI ke PayPal, tanpa harus eksternal apa pun dari kelas yang harus diubah?Saya pikir ini masalah ruang lingkup. Ketika Anda mendefinisikan enkapsulasi (tidak memberi tahu caranya), Anda harus menentukan apa fungsi yang dienkapsulasi.
Kelas apa adanya: apa yang Anda enkapsulasi adalah satu-satunya responsabilitas kelas. Apa yang ia tahu bagaimana melakukannya. Misalnya, menyortir. Jika Anda menyuntikkan beberapa pembanding untuk memesan, katakanlah, klien, itu bukan bagian dari hal yang diringkas: quicksort.
Fungsi yang dikonfigurasikan : jika Anda ingin menyediakan fungsionalitas yang siap digunakan maka Anda tidak menyediakan kelas QuickSort, tetapi instance kelas QuickSort yang dikonfigurasikan dengan Pembanding. Dalam hal ini, kode yang bertanggung jawab untuk membuat dan mengonfigurasi itu harus disembunyikan dari kode pengguna. Dan itu enkapsulasi.
Saat Anda memprogram kelas, menerapkan tanggung jawab tunggal ke dalam kelas, Anda menggunakan opsi 1.
Ketika Anda memprogram aplikasi, itu adalah, membuat sesuatu yang melakukan beberapa pekerjaan konkret yang berguna maka Anda berulang kali menggunakan opsi 2.
Ini adalah implementasi dari instance yang dikonfigurasi:
Ini adalah cara beberapa kode klien lain menggunakannya:
Itu dirangkum karena jika Anda mengubah implementasi (Anda mengubah
clientSorter
definisi kacang) itu tidak merusak penggunaan klien. Mungkin, saat Anda menggunakan file xml dengan semua yang ditulis bersama, Anda melihat semua detail. Tapi percayalah, kode klien (ClientService
) tidak tahu apa-apa tentang penyortirnya.sumber
Mungkin perlu disebutkan bahwa
Encapsulation
agak tergantung perspektif.Dari perspektif seseorang yang bekerja di
A
kelas, dalam contoh keduaA
tahu banyak tentang sifatthis.b
Sedangkan tanpa DI
vs.
Orang yang melihat kode ini tahu lebih banyak tentang sifat dari
A
contoh kedua.Dengan DI, setidaknya semua pengetahuan yang bocor itu ada di satu tempat.
sumber