Apa manfaat injeksi ketergantungan dalam kasus di mana hampir setiap orang membutuhkan akses ke struktur data umum?

20

Ada banyak alasan mengapa global jahat di OOP.

Jika jumlah atau ukuran objek yang perlu dibagi terlalu besar untuk secara efisien diteruskan dalam parameter fungsi, biasanya semua orang merekomendasikan Dependency Injection daripada objek global.

Namun, dalam kasus di mana hampir semua orang perlu tahu tentang struktur data tertentu, mengapa Ketergantungan Injeksi lebih baik daripada objek global?

Contoh (yang disederhanakan, untuk menunjukkan titik secara umum, tanpa menggali terlalu dalam pada aplikasi tertentu)

Ada sejumlah kendaraan virtual yang memiliki sejumlah besar properti dan status, dari jenis, nama, warna, hingga kecepatan, posisi, dll. Sejumlah pengguna dapat mengendalikan mereka secara jarak jauh, dan sejumlah besar peristiwa (baik pengguna- dimulai dan otomatis) dapat mengubah banyak status atau properti mereka.

Solusi naif adalah dengan hanya membuat wadah global mereka, seperti

vector<Vehicle> vehicles;

yang dapat diakses dari mana saja.

Solusi yang lebih ramah-OOP adalah membuat wadah menjadi anggota kelas yang menangani loop peristiwa utama, dan digunakan di konstruktornya. Setiap kelas yang membutuhkannya, dan anggota dari utas utama, akan diberikan akses ke wadah melalui pointer di konstruktor mereka. Misalnya, jika pesan eksternal masuk melalui koneksi jaringan, kelas (satu untuk setiap koneksi) yang menangani parsing akan mengambil alih, dan parser akan memiliki akses ke wadah melalui pointer atau referensi. Sekarang jika pesan yang diuraikan menghasilkan perubahan pada elemen wadah, atau mengharuskan beberapa data keluar untuk melakukan suatu tindakan, itu dapat ditangani tanpa perlu melemparkan sekitar ribuan variabel melalui sinyal dan slot (atau lebih buruk, menyimpannya di parser untuk kemudian diambil oleh orang yang disebut parser). Tentu saja, semua kelas yang menerima akses ke wadah melalui injeksi ketergantungan, adalah bagian dari utas yang sama. Utas yang berbeda tidak akan langsung mengaksesnya, tetapi melakukan tugasnya dan kemudian mengirim sinyal ke utas utama, dan slot di utas utama akan memperbarui wadah.

Namun, jika sebagian besar kelas akan mendapatkan akses ke wadah, apa yang membuatnya sangat berbeda dari global? Jika begitu banyak kelas membutuhkan data dalam wadah, bukankah "cara injeksi ketergantungan" hanya global yang disamarkan?

Satu jawaban adalah keamanan utas: meskipun saya berhati-hati untuk tidak menyalahgunakan wadah global, mungkin pengembang lain di masa depan, di bawah tekanan tenggat waktu yang dekat, namun akan menggunakan wadah global dalam utas yang berbeda, tanpa mengurus semua kasus tabrakan. Namun, bahkan dalam kasus injeksi dependensi, seseorang dapat memberikan pointer kepada seseorang yang menjalankan thread lain, yang mengarah ke masalah yang sama.

vsz
sumber
6
Tunggu, Anda berbicara tentang global yang tidak bisa berubah dan menggunakan tautan ke "mengapa keadaan global buruk" untuk membenarkannya? WTF?
Telastyn
1
Saya tidak membenarkan global sama sekali. Saya pikir jelas bahwa saya setuju dengan fakta bahwa global bukan solusi yang baik. Saya hanya berbicara tentang situasi ketika menggunakan injeksi ketergantungan tidak bisa lebih baik daripada (atau bahkan mungkin hampir tidak bisa dibedakan dari) global. Jadi jawaban mungkin menunjukkan manfaat tersembunyi lainnya dari masih menggunakan suntikan ketergantungan dalam situasi ini, atau bahwa pendekatan lain akan lebih baik daripada di
vsz
9
Tetapi ada perbedaan yang diputuskan antara hanya baca global dan negara global.
Telastyn
1
@vsz tidak benar-benar - pertanyaannya adalah tentang pabrik locator layanan sebagai cara mengatasi masalah banyak objek yang berbeda; tetapi jawabannya juga menjawab pertanyaan Anda tentang memungkinkan kelas mengakses data global daripada data global yang diteruskan ke kelas.
gbjbaanb

Jawaban:

37

dalam kasus di mana hampir semua orang perlu tahu tentang struktur data tertentu, mengapa Dependency Injection lebih baik daripada objek global?

Ketergantungan injeksi adalah hal terbaik sejak mengiris roti , sementara objek global telah dikenal selama beberapa dekade sebagai sumber dari semua kejahatan , jadi ini adalah pertanyaan yang agak menarik.

Titik injeksi ketergantungan tidak hanya untuk memastikan bahwa setiap aktor yang membutuhkan sumber daya dapat memilikinya, karena jelas, jika Anda membuat semua sumber daya global, maka setiap aktor akan memiliki akses ke setiap sumber daya, masalah diselesaikan, bukan?

Titik injeksi ketergantungan adalah:

  1. Untuk memungkinkan para aktor mengakses sumber daya berdasarkan kebutuhan , dan
  2. Untuk memiliki kontrol atas instance sumber daya mana yang diakses oleh aktor tertentu.

Fakta bahwa dalam konfigurasi khusus Anda, semua aktor kebetulan memerlukan akses ke sumber daya yang sama tidak relevan. Percayalah, suatu hari Anda akan perlu mengkonfigurasi ulang hal-hal sehingga para aktor akan memiliki akses ke berbagai contoh sumber daya, dan kemudian Anda akan menyadari bahwa Anda telah melukis diri sendiri di sudut. Beberapa jawaban sudah menunjuk konfigurasi seperti itu: pengujian .

Contoh lain: misalkan Anda membagi aplikasi Anda menjadi client-server. Semua aktor di klien menggunakan set sumber daya pusat yang sama pada klien, dan semua aktor di server menggunakan set sumber daya pusat yang sama di server. Sekarang anggaplah, suatu hari, bahwa Anda memutuskan untuk membuat versi "mandiri" dari aplikasi server-klien Anda, di mana klien dan server dikemas dalam satu executable dan berjalan di mesin virtual yang sama. (Atau lingkungan runtime, tergantung pada bahasa pilihan Anda.)

Jika Anda menggunakan injeksi dependensi, Anda dapat dengan mudah memastikan bahwa semua aktor klien diberi instance sumber daya klien untuk bekerja dengannya, sementara semua aktor server menerima instance sumber daya server.

Jika Anda tidak menggunakan injeksi ketergantungan, Anda benar-benar kurang beruntung, karena hanya satu instance global dari setiap sumber daya yang dapat ada dalam satu mesin virtual.

Kemudian, Anda harus mempertimbangkan: apakah semua aktor benar-benar membutuhkan akses ke sumber daya itu? sangat?

Ada kemungkinan bahwa Anda telah membuat kesalahan dengan mengubah sumber daya itu menjadi objek dewa, (jadi, tentu saja semua orang membutuhkan akses ke sana), atau mungkin Anda terlalu melebih-lebihkan jumlah aktor dalam proyek Anda yang benar - benar membutuhkan akses ke sumber daya itu. .

Dengan global, setiap baris kode sumber di seluruh aplikasi Anda memiliki akses ke setiap sumber daya global. Dengan injeksi ketergantungan, setiap instance sumber daya hanya dapat dilihat oleh para aktor yang benar-benar membutuhkannya. Jika keduanya sama, (aktor yang membutuhkan sumber daya tertentu terdiri dari 100% dari baris kode sumber dalam proyek Anda,) maka Anda pasti telah membuat kesalahan dalam desain Anda. Jadi, baik

  • Refactor sumber daya dewa besar yang besar menjadi sub-sumber daya yang lebih kecil, sehingga aktor yang berbeda memerlukan akses ke bagian yang berbeda, tetapi jarang aktor membutuhkan semua bagiannya, atau

  • Refactor aktor Anda untuk menerima sebagai parameter hanya himpunan bagian dari masalah yang perlu mereka selesaikan, sehingga mereka tidak harus berkonsultasi dengan sumber daya besar yang sangat besar sepanjang waktu.

Mike Nakis
sumber
Saya tidak yakin saya mengerti ini - di satu sisi Anda mengatakan DI memungkinkan Anda untuk menggunakan beberapa contoh sumber daya dan global tidak, yang jelas-jelas omong kosong kecuali jika Anda mengkonfigurasikan variabel statis tunggal terhadap objek berlipat ganda instantiated - tidak ada alasan bahwa "global" harus berupa instance tunggal atau kelas tunggal.
gbjbaanb
3
@ gbjbaanb Misalkan sumber dayanya adalah Zork. OP mengatakan bahwa tidak hanya setiap objek dalam sistemnya memerlukan Zork untuk dikerjakan, tetapi juga, bahwa hanya satu Zork yang akan ada, dan bahwa semua objeknya hanya perlu akses ke satu instance Zork tersebut. Saya mengatakan bahwa tak satu pun dari kedua asumsi ini masuk akal: mungkin tidak benar bahwa semua objeknya memerlukan Zork, dan akan ada saat-saat ketika beberapa objek akan membutuhkan instance Zork tertentu, sedangkan objek lain akan membutuhkan contoh berbeda dari Zork.
Mike Nakis
2
@ gbjbaanb Saya tidak mengira Anda meminta saya untuk menjelaskan mengapa bodoh jika memiliki dua instance global Zork, beri nama ZorkA dan ZorkB, dan hard-code ke objek yang akan digunakan ZorkA dan mana yang akan menggunakan ZorkB, kanan?
Mike Nakis
1
Saya tidak mengatakan semua orang, saya mengatakan hampir semua orang. Inti pertanyaannya adalah apakah DI memiliki manfaat lain selain menolak akses dari beberapa aktor yang tidak membutuhkannya. Saya tahu bahwa "objek dewa" adalah anti-pola, tetapi mengikuti praktik terbaik secara membabi buta juga bisa menjadi satu. Dapat terjadi bahwa seluruh tujuan program adalah untuk melakukan berbagai hal dengan sumber daya tertentu, dalam hal ini hampir semua orang membutuhkan akses ke sumber daya itu. Contoh yang baik adalah program yang bekerja pada gambar: hampir semua yang ada di dalamnya ada hubungannya dengan data gambar.
vsz
1
@ gbjbaanb Saya percaya idenya adalah untuk memiliki satu kelas klien yang dapat bekerja secara eksklusif pada ZorkA atau ZorkB seperti yang ditunjukkan, tidak memiliki kelas klien memutuskan yang mana dari mereka untuk ambil.
Eugene Ryabtsev
39

Ada banyak alasan mengapa global yang tidak bisa berubah itu jahat di OOP.

Itu adalah klaim yang meragukan. Tautan yang Anda gunakan sebagai bukti merujuk pada negara yang bisa berubah - global. Mereka jelas jahat. Baca saja global hanya konstanta. Konstanta relatif waras. Maksud saya, Anda tidak akan menyuntikkan nilai pi ke semua kelas Anda, bukan?

Jika jumlah atau ukuran objek yang membutuhkan pembagian terlalu besar untuk secara efisien diteruskan dalam parameter fungsi, biasanya semua orang merekomendasikan Dependency Injection daripada objek global.

Tidak, mereka tidak.

Jika fungsi / kelas Anda memiliki terlalu banyak dependensi, orang merekomendasikan untuk berhenti dan melihat mengapa desain Anda sangat buruk. Memiliki banyak dependensi merupakan tanda bahwa kelas / fungsi Anda mungkin melakukan terlalu banyak hal dan / atau Anda tidak memiliki abstraksi yang memadai.

Ada sejumlah kendaraan virtual yang memiliki sejumlah besar properti dan status, dari jenis, nama, warna, hingga kecepatan, posisi, dll. Sejumlah pengguna dapat mengendalikan mereka secara jarak jauh, dan sejumlah besar peristiwa (baik pengguna- dimulai dan otomatis) dapat mengubah banyak status atau properti mereka.

Ini adalah mimpi buruk desain. Anda memiliki banyak hal yang dapat digunakan / disalahgunakan tanpa cara validasi, tidak ada batasan konkurensi - itu hanya penggabungan seluruh.

Namun, jika sebagian besar kelas akan mendapatkan akses ke wadah, apa yang membuatnya sangat berbeda dari global? Jika begitu banyak kelas membutuhkan data dalam wadah, bukankah "cara injeksi ketergantungan" hanya global yang disamarkan?

Ya, memiliki penggabungan ke data umum di mana - mana tidak terlalu berbeda dari global, dan karenanya, masih buruk.

Sisi positifnya adalah Anda memisahkan konsumen dari variabel global itu sendiri. Ini memberi Anda beberapa fleksibilitas dalam bagaimana instance dibuat. Itu juga tidak memaksa Anda untuk hanya memiliki satu instance. Anda dapat membuat yang berbeda untuk diteruskan ke konsumen yang berbeda. Anda juga dapat membuat implementasi yang berbeda dari antarmuka Anda per konsumen jika Anda perlu.

Dan karena perubahan yang tak terhindarkan datang dengan persyaratan bergeser dari perangkat lunak, tingkat fleksibilitas dasar seperti itu sangat penting .

Telastyn
sumber
maaf atas kebingungan dengan "tidak bisa berubah", saya ingin mengatakan bisa berubah, dan entah bagaimana tidak mengenali kesalahan saya.
vsz
"Ini adalah mimpi buruk desain" - Aku tahu itu. Tetapi jika persyaratannya adalah bahwa semua peristiwa tersebut harus dapat melakukan perubahan dalam data, maka peristiwa tersebut akan digabungkan ke data bahkan jika saya membelah dan menyembunyikannya di bawah lapisan abstraksi. Seluruh program adalah tentang data ini. Sebagai contoh, jika suatu program harus melakukan banyak operasi berbeda pada suatu gambar, dari semua atau hampir semua kelasnya entah bagaimana akan digabungkan ke data gambar. Hanya mengatakan "mari kita menulis sebuah program yang melakukan sesuatu yang berbeda" tidak dapat diterima.
vsz
2
Aplikasi yang memiliki banyak objek yang secara rutin mengakses beberapa basis data belum pernah terjadi sebelumnya.
Robert Harvey
@ RobertTarvey - tentu saja, tetapi apakah antarmuka yang mereka andalkan "dapat mengakses database" atau "beberapa penyimpanan data persisten untuk foos"?
Telastyn
1
Mengingatkan saya pada Dilbert di mana PHB meminta 500 fitur dan Dilbert mengatakan tidak. Jadi PHB meminta 1 fitur dan Dilbert mengatakan yakin. Hari berikutnya Dilbert mendapat 500 permintaan masing-masing untuk 1 fitur. Jika ada sesuatu yang tidak berskala, itu hanya tidak berskala tidak peduli bagaimana Anda berpakaian.
corsiKa
16

Ada tiga alasan utama yang harus Anda pertimbangkan.

  1. Keterbacaan. Jika setiap unit kode memiliki segala yang dibutuhkan untuk bekerja baik yang disuntikkan atau diteruskan sebagai parameter, mudah untuk melihat kode dan langsung melihat apa yang dilakukannya. Ini memberi Anda lokalitas fungsi, yang juga memungkinkan Anda untuk memisahkan masalah dengan lebih baik dan juga memaksa Anda untuk memikirkan ...
  2. Modularitas. Mengapa parser harus tahu tentang seluruh daftar kendaraan dan semua propertinya? Mungkin tidak. Mungkin perlu menanyakan apakah id kendaraan ada atau apakah kendaraan X memiliki properti Y. Hebat, itu berarti Anda dapat menulis layanan yang melakukan itu, dan menyuntikkan hanya layanan itu ke parser Anda. Tiba-tiba Anda berakhir dengan desain yang jauh lebih masuk akal, dengan setiap bit kode hanya menangani data yang relevan dengannya. Yang menuntun kita ke ...
  3. Testabilitas. Untuk pengujian unit tipikal, Anda ingin mengatur lingkungan Anda untuk berbagai skenario dan injeksi membuatnya sangat mudah untuk diterapkan. Sekali lagi, kembali ke contoh parser, apakah Anda benar-benar ingin selalu membuat kerajinan tangan seluruh daftar kendaraan yang lengkap untuk setiap test case yang Anda tulis untuk parser Anda? Atau apakah Anda hanya akan membuat implementasi tiruan dari objek layanan yang disebutkan di atas, mengembalikan jumlah id yang berbeda-beda? Saya tahu yang mana yang saya pilih.

Jadi, ringkasnya: DI bukan tujuan, itu hanya alat yang memungkinkan untuk mencapai tujuan. Jika Anda menyalahgunakannya (seperti yang Anda lakukan dalam contoh), Anda tidak akan mendekati tujuan, tidak ada properti magis DI yang akan membuat desain yang buruk menjadi baik.

biziclop
sumber
+1 untuk jawaban singkat yang fokus pada masalah perawatan. Lebih banyak P: jawaban SE harus seperti ini.
dodgethesteamroller
1
Jumlah Anda sangat bagus. Bagus sekali. Jika Anda berpikir [pola desain bulan ini] atau [kata kunci bulan ini] secara ajaib akan menyelesaikan masalah Anda, Anda akan memiliki waktu yang buruk.
corsiKa
@corsiKa Saya tidak suka pada DI (atau Spring, lebih tepatnya) untuk waktu yang lama karena saya tidak bisa mengerti intinya. Sepertinya banyak sekali kerumitan tanpa keuntungan nyata (ini kembali pada hari-hari konfigurasi XML). Saya berharap saya telah menemukan beberapa dokumentasi tentang apa yang sebenarnya saya beli dari Anda saat itu. Tetapi saya tidak melakukannya, jadi saya harus mencari tahu sendiri. Butuh waktu yang lama. :)
biziclop
3

Ini benar-benar tentang testability - biasanya objek global sangat mudah dikelola dan mudah dibaca / dimengerti (setelah Anda tahu di sana, tentu saja) tetapi itu adalah perlengkapan permanen dan ketika Anda datang untuk membagi kelas Anda menjadi tes terisolasi, Anda menemukan bahwa Anda objek global macet mengatakan "bagaimana dengan saya" dan dengan demikian tes terisolasi Anda memerlukan objek global untuk dimasukkan di dalamnya. Tidak membantu bahwa sebagian besar kerangka kerja pengujian tidak dengan mudah mendukung skenario ini.

Jadi ya, ini masih bersifat global. Alasan mendasar untuk memilikinya tidak berubah, hanya cara mengaksesnya.

Adapun inisialisasi, ini masih menjadi masalah - Anda masih harus mengatur konfigurasi agar tes Anda dapat berjalan, sehingga Anda tidak mendapatkan apa pun di sana. Saya kira Anda dapat membagi objek bergantung besar menjadi banyak yang lebih kecil dan meneruskan yang sesuai ke kelas yang membutuhkannya, tetapi Anda mungkin mendapatkan kompleksitas yang lebih besar daripada manfaat apa pun.

Terlepas dari semua itu, ini adalah contoh dari 'ekor mengibas-ngibaskan anjing', kebutuhan untuk menguji mendorong bagaimana basis kode dirancang (masalah serupa muncul dengan item seperti kelas statis, misalnya datetime saat ini).

Secara pribadi saya lebih suka menempelkan semua data global saya di objek global (atau beberapa) tetapi menyediakan aksesor untuk mendapatkan bit yang dibutuhkan kelas saya. Lalu saya bisa mengejek mereka untuk mengembalikan data yang saya inginkan ketika datang ke pengujian tanpa menambah kompleksitas DI.

gbjbaanb
sumber
"biasanya objek global sangat dapat dipertahankan" - tidak jika itu bisa berubah. Dalam pengalaman saya, memiliki begitu banyak negara global yang bisa berubah bisa menjadi mimpi buruk (meskipun ada cara untuk membuatnya tertahankan).
sleske
3

Selain jawaban yang sudah Anda miliki,

Ada sejumlah kendaraan virtual yang memiliki sejumlah besar properti dan status, dari jenis, nama, warna, hingga kecepatan, posisi, dll. Sejumlah pengguna dapat mengendalikan mereka secara jarak jauh, dan sejumlah besar peristiwa (baik pengguna- dimulai dan otomatis) dapat mengubah banyak status atau properti mereka.

Salah satu karakteristik pemrograman OOP adalah hanya menyimpan properti yang benar-benar Anda butuhkan untuk objek tertentu,

SEKARANG JIKA objek Anda memiliki terlalu banyak properti - Anda harus memecah objek Anda lebih jauh menjadi sub-objek.

Edit

Sudah disebutkan mengapa objek global buruk, saya akan menambahkan satu contoh untuk itu.

Anda perlu melakukan pengujian unit pada kode GUI formulir windows, jika Anda menggunakan global, Anda harus membuat semua tombol dan acara itu misalnya untuk membuat kasus pengujian yang tepat ...

Matematika
sumber
Benar, tetapi misalnya, parser akan diminta untuk mengetahui segalanya karena perintah apa pun yang mungkin diterima yang dapat membuat perubahan apa pun.
vsz
1
"Sebelum saya sampai pada pertanyaan Anda yang sebenarnya" ... apakah Anda akan menjawab pertanyaannya?
gbjbaanb
@ gbjbaanb pertanyaan telah mendapat banyak teks, izinkan saya beberapa waktu untuk membacanya, dengan benar
Matematika