Ketergantungan injeksi melalui konstruktor atau seter properti?

151

Saya refactoring kelas dan menambahkan ketergantungan baru ke sana. Kelas saat ini mengambil dependensi yang ada di konstruktor. Jadi untuk konsistensi, saya menambahkan parameter ke konstruktor.
Tentu saja, ada beberapa subclass plus bahkan lebih banyak untuk unit test, jadi sekarang saya memainkan permainan berkeliling mengubah semua konstruktor untuk mencocokkan, dan itu butuh waktu lama.
Itu membuat saya berpikir bahwa menggunakan properti dengan setter adalah cara yang lebih baik untuk mendapatkan dependensi. Saya tidak berpikir dependensi yang disuntikkan harus menjadi bagian dari antarmuka untuk membangun sebuah instance dari sebuah kelas. Anda menambahkan dependensi dan sekarang semua pengguna Anda (subclass dan siapa pun yang memberi Anda instantiasi secara langsung) tiba-tiba mengetahuinya. Rasanya seperti istirahat enkapsulasi.

Ini tampaknya bukan pola dengan kode yang ada di sini, jadi saya mencari tahu apa konsensus umum, pro dan kontra dari konstruktor versus properti. Apakah menggunakan setter properti lebih baik?

Niall Connaughton
sumber

Jawaban:

126

Yah, itu tergantung :-).

Jika kelas tidak dapat melakukan tugasnya tanpa ketergantungan, maka tambahkan ke konstruktor. Kelas membutuhkan dependensi baru, sehingga Anda ingin perubahan Anda memecahkan banyak hal. Juga, membuat kelas yang tidak sepenuhnya diinisialisasi ("konstruksi dua langkah") adalah anti-pola (IMHO).

Jika kelas dapat bekerja tanpa ketergantungan, setter baik-baik saja.

sleske
sumber
11
Saya pikir itu banyak kasus, lebih baik menggunakan pola Obyek Null dan tetap dengan memerlukan referensi pada konstruktor. Ini menghindari semua pemeriksaan nol dan meningkatnya kompleksitas siklomatik.
Mark Lindell
3
@ Mark: Poin bagus. Namun, pertanyaannya adalah tentang menambahkan ketergantungan ke kelas yang ada. Kemudian menjaga konstruktor no-arg memungkinkan kompatibilitas ke belakang.
sleske
Bagaimana dengan kapan dependensi diperlukan untuk berfungsi, tetapi injeksi default dari dependensi itu biasanya sudah cukup. Maka haruskah ketergantungan itu "diabaikan" oleh properti atau konstruktor kelebihan beban?
Patrick Szalapski
@ Patrick: Dengan "kelas tidak dapat melakukan tugasnya tanpa ketergantungan", maksud saya bahwa tidak ada standar yang masuk akal (misalnya kelas membutuhkan koneksi DB). Dalam situasi Anda, keduanya akan bekerja. Saya biasanya masih memilih untuk pendekatan konstruktor, karena mengurangi kompleksitas (bagaimana jika misalnya penyetel dipanggil dua kali)?
sleske
1
Tulisan yang
Eldhose Abraham
21

Para pengguna kelas seharusnya tahu tentang dependensi kelas yang diberikan. Jika saya memiliki kelas yang, misalnya, terhubung ke database, dan tidak menyediakan sarana untuk menyuntikkan ketergantungan lapisan persistensi, pengguna tidak akan pernah tahu bahwa koneksi ke database harus tersedia. Namun, jika saya mengubah konstruktor saya memberi tahu pengguna bahwa ada ketergantungan pada lapisan ketekunan.

Juga, untuk mencegah diri Anda harus mengubah setiap penggunaan konstruktor lama, cukup menerapkan chaining konstruktor sebagai jembatan sementara antara konstruktor lama dan baru.

public class ClassExample
{
    public ClassExample(IDependencyOne dependencyOne, IDependencyTwo dependencyTwo)
        : this (dependnecyOne, dependencyTwo, new DependnecyThreeConcreteImpl())
    { }

    public ClassExample(IDependencyOne dependencyOne, IDependencyTwo dependencyTwo, IDependencyThree dependencyThree)
    {
        // Set the properties here.
    }
}

Salah satu poin injeksi ketergantungan adalah untuk mengungkapkan dependensi apa yang dimiliki kelas. Jika kelas memiliki terlalu banyak dependensi, maka mungkin inilah saatnya refactoring dilakukan: Apakah setiap metode dalam kelas menggunakan semua dependensi? Jika tidak, maka itu adalah titik awal yang baik untuk melihat di mana kelas dapat dibagi.

Dokter Blue
sumber
Tentu saja chaining konstruktor hanya berfungsi jika ada nilai default yang wajar untuk parameter baru. Tapi jika tidak, anda tidak dapat menghindari melanggar hal-hal pula ...
sleske
Biasanya, Anda akan menggunakan apa pun yang Anda gunakan dalam metode sebelum injeksi dependensi sebagai parameter default. Idealnya, ini akan membuat penambahan konstruktor baru menjadi refactoring bersih, karena perilaku kelas tidak akan berubah.
Dokter Biru
1
Saya mengambil poin Anda tentang ketergantungan pengelolaan sumber daya seperti koneksi database. Saya pikir masalah dalam kasus saya adalah kelas yang saya tambahkan ketergantungan memiliki beberapa subclass. Dalam dunia wadah IOC di mana properti akan ditetapkan oleh wadah, menggunakan setter setidaknya akan mengurangi tekanan pada duplikasi antarmuka konstruktor antara semua subclass.
Niall Connaughton
17

Tentu saja, mengenakan konstruktor berarti Anda dapat memvalidasi sekaligus. Jika Anda menetapkan hal-hal ke dalam bidang read-only maka Anda memiliki beberapa jaminan tentang dependensi objek Anda sejak waktu konstruksi.

Ini adalah rasa sakit yang nyata menambahkan dependensi baru, tetapi setidaknya dengan cara ini kompiler terus mengeluh sampai benar. Itu hal yang baik, saya pikir.

Joe
sumber
Plus satu untuk ini. Selain itu, ini sangat mengurangi bahaya ketergantungan sirkular ...
gilgamash
11

Jika Anda memiliki banyak dependensi opsional (yang sudah tercium) maka mungkin injeksi setter adalah cara yang harus dilakukan. Injeksi konstruktor lebih baik mengungkapkan ketergantungan Anda.

epitka
sumber
11

Pendekatan umum yang disukai adalah menggunakan injeksi konstruktor sebanyak mungkin.

Injeksi konstruktor secara tepat menyatakan apa saja dependensi yang diperlukan agar objek berfungsi dengan benar - tidak ada yang lebih menjengkelkan daripada membuat objek baru dan membuatnya crash ketika memanggil metode di atasnya karena beberapa dependensi tidak diatur. Objek yang dikembalikan oleh konstruktor harus dalam kondisi aktif.

Cobalah untuk hanya memiliki satu konstruktor, itu membuat desain tetap sederhana dan menghindari ambiguitas (jika bukan untuk manusia, untuk wadah DI).

Anda dapat menggunakan injeksi properti ketika Anda memiliki apa yang oleh Mark Seemann disebut default lokal di bukunya "Dependency Injection in .NET": ketergantungan adalah opsional karena Anda dapat memberikan implementasi yang berfungsi baik tetapi ingin mengizinkan penelepon menentukan yang berbeda jika dibutuhkan.

(Mantan jawaban di bawah)


Saya pikir injeksi konstruktor lebih baik jika injeksi wajib. Jika ini menambahkan terlalu banyak konstruktor, pertimbangkan untuk menggunakan pabrik dan bukan konstruktor.

Injeksi setter baik jika injeksi opsional, atau jika Anda ingin mengubahnya setengah jalan. Saya biasanya tidak suka setter, tapi ini masalah selera.

Philippe
sumber
Saya berpendapat bahwa biasanya mengubah injeksi setengah jalan adalah gaya yang buruk (karena Anda menambahkan keadaan tersembunyi ke objek Anda). Tapi ada aturan w / o pengecualian tentu saja ...
sleske
Yap, itu sebabnya saya bilang saya tidak terlalu suka setter ... Saya suka pendekatan konstruktor karena itu tidak bisa diubah.
Philippe
"Jika ini menambahkan terlalu banyak konstruktor, pertimbangkan untuk menggunakan pabrik dan bukan konstruktor." Anda pada dasarnya menunda pengecualian waktu berjalan dan bahkan mungkin mendapatkan kesalahan dan akhirnya terjebak dengan implementasi pencari lokasi layanan.
MeTitus
@Marco itu adalah jawaban saya sebelumnya dan Anda benar. Jika ada banyak konstruktor, saya berpendapat bahwa kelas melakukan terlalu banyak hal :-) Atau pertimbangkan pabrik abstrak.
Philippe
7

Ini sebagian besar masalah selera pribadi. Secara pribadi saya cenderung lebih suka injeksi penyetel, karena saya percaya itu memberi Anda lebih banyak fleksibilitas dalam cara yang Anda bisa mengganti implementasi saat runtime. Selain itu, konstruktor dengan banyak argumen tidak bersih menurut pendapat saya, dan argumen yang disediakan dalam konstruktor harus dibatasi pada argumen non-opsional.

Selama antarmuka kelas (API) jelas dalam apa yang diperlukan untuk melakukan tugasnya, Anda baik.

nkr1pt
sumber
tolong jelaskan mengapa Anda downvoting?
nkr1pt
3
Ya, konstruktor dengan banyak argumen buruk. Itu sebabnya Anda mereformasi kelas dengan banyak parameter konstruktor :-).
sleske
10
@ nkr1pt: Kebanyakan orang (termasuk saya) setuju bahwa injeksi setter buruk jika memungkinkan Anda untuk membuat kelas yang gagal saat runtime jika injeksi tidak dilakukan. Karena itu saya yakin seseorang keberatan dengan pernyataan Anda bahwa itu adalah selera pribadi.
sleske
7

Saya pribadi lebih suka "pola" Ekstrak dan Timpa daripada menyuntikkan dependensi dalam konstruktor, sebagian besar karena alasan yang diuraikan dalam pertanyaan Anda. Anda bisa mengatur properti sebagai virtualdan kemudian menimpa implementasi di kelas yang dapat diuji yang diturunkan.

Russ Cam
sumber
1
Saya pikir nama resmi polanya adalah "Metode Templat".
davidjnelson
6

Saya lebih suka injeksi konstruktor karena ini membantu "menegakkan" persyaratan ketergantungan kelas. Jika ada dalam c'tor, konsumen harus mengatur objek untuk mendapatkan kompilasi aplikasi. Jika Anda menggunakan suntikan setter, mereka mungkin tidak tahu bahwa mereka memiliki masalah sampai waktu berjalan - dan tergantung pada objeknya, itu mungkin terlambat dalam waktu berjalan.

Saya masih menggunakan injeksi setter dari waktu ke waktu ketika objek yang diinjeksi mungkin membutuhkan banyak pekerjaan itu sendiri, seperti inisialisasi.

ctacke
sumber
6

Saya menolak injeksi konstruktor, karena ini tampaknya paling logis. Seperti mengatakan kelas saya membutuhkan dependensi ini untuk melakukan tugasnya. Jika ini merupakan ketergantungan opsional maka properti tampak masuk akal.

Saya juga menggunakan injeksi properti untuk mengatur hal-hal yang tidak memiliki referensi ke wadah seperti ASP.NET View pada presenter yang dibuat menggunakan wadah.

Saya tidak berpikir itu merusak enkapsulasi. Pekerjaan batin harus tetap internal dan ketergantungan menangani masalah yang berbeda.

David Kiff
sumber
Terima kasih atas jawaban anda. Tampaknya konstruktor adalah jawaban populer. Namun saya pikir itu memecah enkapsulasi dalam beberapa cara. Injeksi pra-ketergantungan, kelas akan mendeklarasikan dan membuat instance jenis konkret yang diperlukan untuk melakukan tugasnya. Dengan DI, subclass (dan instantiator manual) sekarang tahu alat apa yang digunakan kelas dasar. Anda menambahkan ketergantungan baru dan sekarang Anda harus rantai instance melalui dari semua subclass, bahkan jika mereka tidak perlu menggunakan dependensi itu sendiri.
Niall Connaughton
Baru saja menulis jawaban panjang yang bagus dan kehilangannya karena pengecualian di situs ini !! :( Singkatnya, baseclass biasanya digunakan untuk menggunakan kembali logika. Logika ini bisa dengan mudah masuk ke subclass ... sehingga Anda bisa memikirkan baseclass dan subclass = satu perhatian, yang tergantung pada beberapa objek eksternal, yang melakukan pekerjaan yang berbeda. Kenyataannya Anda memiliki ketergantungan, tidak berarti Anda perlu mengekspos apa pun yang sebelumnya Anda jaga tetap pribadi.
David Kiff
3

Salah satu opsi yang mungkin layak dipertimbangkan adalah menyusun multi-dependensi kompleks dari dependensi tunggal sederhana. Artinya, tentukan kelas tambahan untuk dependensi majemuk. Ini membuat segalanya sedikit lebih mudah injeksi konstruktor WRT - lebih sedikit parameter per panggilan - sambil tetap mempertahankan hal-hal yang harus disediakan-semua-dependensi-untuk-instantiate.

Tentu saja sangat masuk akal jika ada semacam pengelompokan dependensi logis, sehingga senyawa lebih dari agregat sewenang-wenang, dan masuk akal jika ada banyak tanggungan untuk dependensi senyawa tunggal - tetapi "parameter" blok parameter memiliki sudah ada sejak lama, dan sebagian besar yang saya lihat cukup sewenang-wenang.

Namun secara pribadi, saya lebih suka menggunakan metode / properti-setter untuk menentukan dependensi, opsi dll. Nama panggilan membantu menjelaskan apa yang sedang terjadi. Adalah ide yang baik untuk memberikan contoh snippet ini-bagaimana-untuk-mengatur-itu, dan pastikan kelas dependen melakukan cukup pengecekan kesalahan. Anda mungkin ingin menggunakan model kondisi hingga untuk pengaturan.

Steve314
sumber
3

Saya baru-baru ini mengalami situasi di mana saya memiliki banyak dependensi di kelas, tetapi hanya satu dependensi yang akan berubah dalam setiap implementasi. Karena akses data dan dependensi logging kesalahan kemungkinan hanya akan diubah untuk tujuan pengujian, saya menambahkan parameter opsional untuk dependensi tersebut dan memberikan implementasi default dari dependensi tersebut dalam kode konstruktor saya. Dengan cara ini, kelas mempertahankan perilaku defaultnya kecuali dikesampingkan oleh konsumen kelas.

Menggunakan parameter opsional hanya dapat dicapai dalam kerangka kerja yang mendukungnya, seperti .NET 4 (untuk C # dan VB.NET, meskipun VB.NET selalu memilikinya). Tentu saja, Anda dapat mencapai fungsi serupa dengan hanya menggunakan properti yang dapat dipindahkan oleh konsumen kelas Anda, tetapi Anda tidak mendapatkan keuntungan dari ketidakberdayaan yang disediakan dengan memiliki objek antarmuka pribadi yang ditugaskan ke parameter konstruktor.

Semua ini dikatakan, jika Anda memperkenalkan ketergantungan baru yang harus disediakan oleh setiap konsumen, Anda harus memperbaiki konstruktor Anda dan semua kode yang konsumen kelas Anda. Saran saya di atas benar-benar hanya berlaku jika Anda memiliki kemewahan untuk dapat memberikan implementasi default untuk semua kode Anda saat ini tetapi masih memberikan kemampuan untuk menimpa implementasi default jika perlu.

Ben McCormack
sumber
1

Ini adalah pos lama, tetapi jika diperlukan di masa depan mungkin ini ada gunanya:

https://github.com/omegamit6zeichen/prinject

Saya punya ide yang sama dan muncul dengan kerangka kerja ini. Ini mungkin jauh dari lengkap, tetapi ini adalah gagasan tentang kerangka kerja yang berfokus pada injeksi properti

Markus
sumber
0

Itu tergantung pada bagaimana Anda ingin menerapkan. Saya lebih suka injeksi konstruktor di mana pun saya merasakan nilai-nilai yang masuk ke implementasi tidak sering berubah. Misalnya: Jika perusahaan compnay pergi dengan server oracle, saya akan mengkonfigurasi nilai datsource saya untuk koneksi yang mencapai kacang melalui injeksi konstruktor. Selain itu, jika aplikasi saya adalah produk dan kemungkinan dapat terhubung ke db pelanggan, saya akan menerapkan konfigurasi db dan implementasi multi-merek melalui injeksi setter. Saya baru saja mengambil contoh tetapi ada cara yang lebih baik untuk mengimplementasikan skenario yang saya sebutkan di atas.

ramarao Gangina
sumber
1
Bahkan ketika coding di rumah, saya selalu kode dari sudut pandang bahwa saya adalah kontraktor independen yang mengembangkan kode yang dimaksudkan untuk didistribusikan kembali. Saya menganggap itu akan menjadi open source. Dengan cara ini, saya cara saya memastikan bahwa kode itu modular dan pluggable dan mengikuti prinsip-prinsip SOLID.
Fred
0

Injeksi konstruktor secara eksplisit mengungkapkan dependensi, membuat kode lebih mudah dibaca dan lebih rentan terhadap kesalahan run-time yang tidak ditangani jika argumen diperiksa dalam konstruktor, tetapi itu benar-benar turun ke pendapat pribadi, dan semakin Anda menggunakan DI semakin Anda akan semakin cenderung bergoyang-goyang satu dan lain cara tergantung pada proyek. Saya pribadi memiliki masalah dengan bau kode seperti konstruktor dengan daftar panjang argumen, dan saya merasa bahwa konsumen suatu objek harus mengetahui dependensi untuk menggunakan objek, jadi ini membuat kasus untuk menggunakan injeksi properti. Saya tidak suka sifat implisit dari injeksi properti, tetapi saya merasa lebih elegan, menghasilkan kode yang tampak lebih bersih. Tetapi di sisi lain, injeksi konstruktor memang menawarkan tingkat enkapsulasi yang lebih tinggi,

Pilih injeksi dengan konstruktor atau berdasarkan properti dengan bijak berdasarkan skenario spesifik Anda. Dan jangan merasa bahwa Anda harus menggunakan DI hanya karena tampaknya perlu dan itu akan mencegah desain dan bau kode yang buruk. Terkadang tidak sepadan dengan upaya untuk menggunakan suatu pola jika upaya dan kompleksitasnya melebihi manfaatnya. Tetap sederhana.

David Spenard
sumber