Haruskah ketergantungan suntikan dilakukan di ctor atau per metode?

16

Mempertimbangkan:

public class CtorInjectionExample
{
    public CtorInjectionExample(ISomeRepository SomeRepositoryIn, IOtherRepository OtherRepositoryIn)
    {
        this._someRepository = SomeRepositoryIn;
        this._otherRepository = OtherRepositoryIn;
    }

    public void SomeMethod()
    {
        //use this._someRepository
    }

    public void OtherMethod()
    {
        //use this._otherRepository
    }
}

melawan:

public class MethodInjectionExample
{
    public MethodInjectionExample()
    {
    }

    public void SomeMethod(ISomeRepository SomeRepositoryIn)
    {
        //use SomeRepositoryIn
    }

    public void OtherMethod(IOtherRepository OtherRepositoryIn)
    {
        //use OtherRepositoryIn
    }
}

Sementara injeksi Ctor membuat ekstensi menjadi sulit (kode apa pun yang memanggil ctor perlu diperbarui ketika dependensi baru ditambahkan), dan injeksi level metode tampaknya tetap lebih dienkapsulasi dari dependensi level kelas dan saya tidak dapat menemukan argumen lain untuk / terhadap pendekatan ini .

Apakah ada pendekatan yang pasti untuk dilakukan injeksi?

(NB Saya sudah mencari informasi tentang ini dan mencoba membuat pertanyaan ini objektif.)

StuperUser
sumber
4
Jika kelas Anda kohesif, maka banyak metode berbeda akan menggunakan bidang yang sama. Ini akan memberi Anda jawaban yang Anda cari ...
Oded
@Oded Poin bagus, kohesi akan sangat memengaruhi keputusan. Jika suatu kelas, katakanlah, kumpulan metode penolong yang masing-masing memiliki dependensi berbeda (tampaknya tidak kohesif, tetapi secara logika serupa) dan yang lain sangat kohesif, mereka masing-masing harus menggunakan pendekatan yang paling menguntungkan. Namun, ini akan menyebabkan ketidakkonsistenan lintas solusi.
StuperUser
Relevan dengan contoh yang saya berikan: en.wikipedia.org/wiki/False_dilemma
StuperUser

Jawaban:

3

Itu tergantung, jika objek yang disuntikkan digunakan oleh lebih dari satu metode di kelas, dan menyuntikkan di dalamnya pada setiap metode tidak masuk akal, Anda perlu menyelesaikan dependensi untuk setiap pemanggilan metode, dan tetapi jika dependensi hanya digunakan oleh satu metode menyuntikkan di dalamnya pada konstruktor tidak baik, tetapi karena Anda mengalokasikan sumber daya yang tidak pernah dapat digunakan.

nohro
sumber
15

Benar, pertama, mari kita berurusan dengan "Setiap kode panggilan ctor akan perlu memperbarui ketika dependensi baru ditambahkan"; Untuk menjadi jelas, jika Anda melakukan injeksi dependensi dan Anda memiliki kode panggilan baru () pada objek dengan dependensi, Anda salah melakukannya .

Wadah DI Anda harus dapat menyuntikkan semua dependensi yang relevan, jadi Anda tidak perlu khawatir mengubah tanda tangan konstruktor, sehingga argumen itu tidak benar-benar berlaku.

Adapun ide injeksi per-metode vs per-kelas, ada dua masalah utama dengan injeksi per-metode.

Satu masalah adalah bahwa metode kelas Anda harus berbagi dependensi, itu adalah salah satu cara untuk membantu memastikan kelas Anda dipisahkan secara efektif, jika Anda melihat kelas dengan sejumlah besar dependensi (mungkin lebih dari 4-5) maka kelas itu adalah kandidat utama untuk refactoring ke dalam dua kelas.

Masalah berikutnya adalah bahwa untuk "menyuntikkan" dependensi, per-metode, Anda harus meneruskannya ke pemanggilan metode. Ini berarti bahwa Anda harus menyelesaikan dependensi sebelum panggilan metode, jadi Anda mungkin akan berakhir dengan sekelompok kode seperti ini:

var someDependency = ServiceLocator.Resolve<ISomeDependency>();
var something = classBeingInjected.DoStuff(someDependency);

Sekarang, katakan Anda akan memanggil metode ini di 10 tempat di sekitar aplikasi Anda: Anda akan memiliki 10 snippet ini. Selanjutnya, katakan Anda perlu menambahkan ketergantungan lain ke DoStuff (): Anda harus mengubah cuplikan itu 10 kali (atau membungkusnya dengan metode, dalam hal ini Anda hanya meniru perilaku DI secara manual, yang merupakan pemborosan waktu).

Jadi, apa yang pada dasarnya Anda lakukan di sana adalah membuat kelas memanfaatkan DI Anda menyadari wadah DI mereka sendiri, yang pada dasarnya adalah ide yang buruk , karena sangat cepat mengarah ke desain kikuk yang sulit untuk dipertahankan.

Bandingkan dengan injeksi konstruktor; Dalam injeksi konstruktor Anda tidak terikat pada wadah DI tertentu, dan Anda tidak pernah secara langsung bertanggung jawab untuk memenuhi dependensi kelas Anda, jadi perawatannya cukup bebas sakit kepala.

Bagi saya sepertinya Anda mencoba menerapkan IoC ke kelas yang berisi banyak metode penolong yang tidak terkait, sedangkan Anda mungkin lebih baik membagi kelas penolong menjadi sejumlah kelas layanan berdasarkan penggunaan, kemudian menggunakan penolong untuk mendelegasikan panggilan. Ini masih bukan pendekatan yang bagus (penolong dikelompokkan dengan metode yang melakukan sesuatu yang lebih kompleks daripada hanya berurusan dengan argumen yang diberikan kepada mereka umumnya hanya kelas layanan yang ditulis dengan buruk), tetapi setidaknya akan membuat desain Anda sedikit lebih bersih .

(NB Saya sudah melakukan pendekatan yang Anda sarankan sebelumnya, dan itu adalah ide yang sangat buruk yang saya belum ulangi sejak itu. Ternyata saya mencoba untuk memisahkan kelas yang benar-benar tidak perlu dipisahkan, dan akhirnya berakhir dengan satu set antarmuka di mana setiap pemanggilan metode membutuhkan pemilihan antarmuka lain yang hampir diperbaiki. Itu adalah mimpi buruk untuk dipertahankan.)

Ed James
sumber
1
"if you're doing dependency injection and you have any code calling new() on an object with dependencies, you're doing it wrong."Jika Anda menguji unit metode pada kelas Anda harus instantiate atau Anda menggunakan DI untuk instantiate Kode Anda Diuji?
StuperUser
@StuperUser Unit testing adalah skenario yang sedikit berbeda, komentar saya hanya benar-benar berlaku untuk kode produksi. Karena Anda akan membuat dependensi tiruan (atau rintisan) untuk pengujian Anda melalui kerangka kerja mengejek, masing-masing dengan perilaku pra-konfigurasi dan serangkaian asumsi dan pernyataan, Anda harus menyuntikkannya dengan tangan.
Ed James
1
Itulah kode yang saya bicarakan ketika saya katakan "any code calling the ctor will need updating", kode produksi saya tidak memanggil ctors. Untuk alasan-alasan ini.
StuperUser
2
@StuperUser Cukup adil, tetapi Anda masih akan memanggil metode dengan dependensi yang diperlukan, yang berarti sisa jawaban saya masih berdiri! Sejujurnya, saya setuju tentang masalah dengan injeksi konstruktor dan memaksa semua dependensi, dari perspektif pengujian unit. Saya cenderung menggunakan injeksi properti, karena ini memungkinkan Anda untuk hanya menyuntikkan dependensi yang Anda harapkan akan diperlukan untuk pengujian, yang saya temukan pada dasarnya adalah serangkaian panggilan Assert.WasNotCalled implisit lainnya tanpa harus benar-benar menulis kode apa pun! : D
Ed James
3

Ada berbagai jenis inversi kontrol , dan pertanyaan Anda hanya mengusulkan dua (Anda juga dapat menggunakan pabrik untuk menyuntikkan ketergantungan).

Jawabannya adalah: itu tergantung kebutuhan. Terkadang lebih baik menggunakan satu jenis, dan jenis lainnya berbeda.

Saya pribadi suka injector injector, karena constructor ada untuk sepenuhnya menginisialisasi objek. Harus memanggil setter nanti membuat objek tidak sepenuhnya dibangun.

BЈовић
sumber
3

Anda melewatkan salah satu yang setidaknya bagi saya adalah injeksi properti paling fleksibel. Saya menggunakan Castle / Windsor dan yang perlu saya lakukan adalah menambahkan properti otomatis baru ke kelas saya dan saya mendapatkan objek yang diinjeksi tanpa masalah dan tanpa merusak antarmuka.

Otávio Décio
sumber
Saya sudah terbiasa dengan aplikasi web dan jika kelas-kelas ini berada di lapisan Domain, yang disebut oleh UI, yang ketergantungannya sudah diselesaikan dengan cara yang mirip dengan Injeksi Properti. Saya bertanya-tanya bagaimana cara menangani kelas yang bisa saya berikan pada objek yang disuntikkan.
StuperUser
Saya juga menyukai injeksi properti, hanya saja Anda masih dapat menggunakan parameter konstruktor normal dengan wadah DI, secara otomatis membedakan antara dependensi yang diselesaikan dan yang tidak diselesaikan. Namun, karena konstruktor dan properti-injeksi secara fungsional identik untuk skenario ini, saya memilih untuk tidak menyebutkannya;)
Ed James
Injeksi properti memaksa setiap objek untuk bisa berubah dan membuat instance dalam keadaan tidak valid. Mengapa itu lebih disukai?
Chris Pitman
2
@ Chris Sebenarnya, dalam kondisi normal, konstruktor dan injeksi properti bekerja cukup identik, dengan objek yang sepenuhnya disuntikkan selama resolusi. Satu-satunya waktu itu dapat dianggap "tidak valid" adalah selama pengujian unit, ketika Anda tidak akan menggunakan wadah DI untuk membuat instantiate implementasi. Lihat komentar saya pada jawaban saya untuk pendapat saya mengapa ini benar-benar bermanfaat. Saya menghargai bahwa mutabilitas dapat menjadi masalah, tetapi secara realistis Anda hanya akan merujuk kelas yang diselesaikan melalui antarmuka mereka, yang tidak akan mengekspos dependensi untuk diedit.
Ed James