Saya mendapatkan injeksi ketergantungan, tetapi bisakah seseorang membantu saya memahami perlunya wadah IoC?

15

Saya minta maaf jika ini sepertinya pengulangan pertanyaan yang lain, tetapi setiap kali saya menemukan artikel mengenai topik tersebut, sebagian besar hanya berbicara tentang apa itu DI. Jadi, saya mendapatkan DI, tetapi saya mencoba memahami perlunya wadah IoC, yang tampaknya semua orang masuki. Apakah titik wadah IoC benar-benar hanya untuk "menyelesaikan otomatis" implementasi konkret dari dependensi? Mungkin kelas saya cenderung tidak memiliki beberapa dependensi dan mungkin itu sebabnya saya tidak melihat masalah besar, tetapi saya ingin memastikan bahwa saya memahami utilitas wadah dengan benar.

Saya biasanya memecah logika bisnis saya menjadi kelas yang mungkin terlihat seperti ini:

public class SomeBusinessOperation
{
    private readonly IDataRepository _repository;

    public SomeBusinessOperation(IDataRespository repository = null)
    {
        _repository = repository ?? new ConcreteRepository();
    }

    public SomeType Run(SomeRequestType request)
    {
        // do work...
        var results = _repository.GetThings(request);

        return results;
    }
}

Jadi itu hanya memiliki satu ketergantungan, dan dalam beberapa kasus mungkin memiliki yang kedua atau ketiga, tetapi tidak terlalu sering. Jadi, apa pun yang memanggil ini dapat lewat repo sendiri atau mengizinkannya menggunakan repo default.

Sejauh pemahaman saya saat ini tentang wadah IoC, semua wadah lakukan adalah menyelesaikan IDataRepository. Tetapi jika hanya itu yang dilakukannya, maka saya tidak melihat satu ton nilai pun di dalamnya karena kelas operasional saya telah menentukan mundur ketika tidak ada ketergantungan yang lewat. Jadi satu-satunya manfaat lain yang dapat saya pikirkan adalah bahwa jika saya memiliki beberapa operasi seperti ini menggunakan repo fallback yang sama, saya bisa mengubah repo itu di satu tempat yang merupakan registry / pabrik / wadah. Dan itu hebat, tetapi apakah itu?

Sinaesthetic
sumber
1
Seringkali memiliki versi cadangan default dependensi tidak terlalu masuk akal.
Ben Aaronson
Bagaimana maksudmu? "Fallback" adalah kelas konkret yang digunakan hampir sepanjang waktu kecuali dalam unit test. Akibatnya, ini akan menjadi kelas yang sama yang terdaftar dalam wadah.
Sinaesthetic
Ya, tetapi dengan wadah: (1) semua objek lain dalam wadah mendapatkan instance yang sama ConcreteRepositorydan (2) Anda dapat menyediakan dependensi tambahan untuk ConcreteRepository(koneksi database akan umum, misalnya).
Jules
@Sestetika, saya tidak bermaksud bahwa itu selalu merupakan ide yang buruk, tetapi seringkali itu tidak sesuai. Misalnya itu akan mencegah Anda mengikuti arsitektur bawang dengan referensi proyek Anda. Juga mungkin tidak ada implementasi standar yang jelas. Dan seperti yang dikatakan Jules, wadah IOC mengelola tidak hanya memungut tipe ketergantungan, tetapi melakukan hal-hal seperti berbagi instance dan manajemen siklus hidup
Ben Aaronson
Saya akan membuat tshirt yang bertuliskan "Parameter Fungsi - Injeksi Ketergantungan ASLI!"
Graham

Jawaban:

2

Kontainer IoC bukan tentang kasus di mana Anda memiliki satu ketergantungan. Ini tentang kasus di mana Anda memiliki 3 dependensi dan mereka memiliki beberapa dependensi yang memiliki dependensi dll.

Ini juga membantu Anda untuk memusatkan resolusi ketergantungan, dan manajemen siklus ketergantungan.

Tanda
sumber
10

Ada sejumlah alasan mengapa Anda mungkin ingin menggunakan wadah IoC.

Dll tidak direferensikan

Anda dapat menggunakan wadah IoC untuk menyelesaikan kelas beton dari dll yang tidak direferensikan. Ini berarti bahwa Anda dapat mengambil dependensi sepenuhnya pada abstraksi - yaitu antarmuka.

Hindari penggunaan new

Wadah IoC berarti bahwa Anda dapat sepenuhnya menghapus penggunaan newkata kunci untuk membuat kelas. Ini memiliki dua efek. Yang pertama adalah itu memisahkan kelas Anda. Yang kedua (yang terkait) adalah bahwa Anda dapat menjatuhkan ejekan untuk pengujian unit. Ini sangat berguna, terutama ketika Anda berinteraksi dengan proses yang berjalan lama.

Menulis Terhadap Abstraksi

Menggunakan wadah IoC untuk menyelesaikan dependensi konkret Anda agar Anda dapat menulis kode Anda terhadap abstraksi, daripada menerapkan setiap kelas konkret yang Anda butuhkan saat dibutuhkan. Misalnya, Anda mungkin memerlukan kode Anda untuk membaca data dari database. Alih-alih menulis kelas interaksi basis data, Anda cukup menulis antarmuka untuknya dan mengkodekaninya. Anda dapat menggunakan tiruan untuk menguji fungsionalitas kode yang Anda kembangkan saat Anda mengembangkannya, daripada mengandalkan mengembangkan kelas interaksi basis data konkret sebelum Anda dapat menguji kode lainnya.

Hindari kode rapuh

Alasan lain untuk menggunakan wadah IoC adalah bahwa dengan mengandalkan wadah IoC untuk menyelesaikan dependensi Anda, Anda menghindari kebutuhan untuk mengubah setiap panggilan tunggal ke konstruktor kelas ketika Anda menambah atau menghapus dependensi. Kontainer IoC akan secara otomatis menyelesaikan dependensi Anda. Ini bukan masalah besar ketika Anda membuat kelas sekali, tetapi ini adalah masalah besar ketika Anda membuat kelas di seratus tempat.

Manajemen seumur hidup dan pembersihan sumber daya yang tidak dikelola

Alasan terakhir yang akan saya sebutkan adalah manajemen daya tahan objek. Kontainer IoC sering memberikan kemampuan untuk menentukan umur suatu objek. Masuk akal untuk menentukan masa pakai objek dalam wadah IoC daripada mencoba mengelolanya secara manual dalam kode. Manajemen seumur hidup manual bisa sangat sulit. Ini bisa berguna ketika berhadapan dengan benda yang membutuhkan pembuangan. Alih-alih mengelola pembuangan objek secara manual, beberapa wadah IoC akan mengelola pembuangan untuk Anda, yang dapat membantu mencegah kebocoran memori dan menyederhanakan basis kode Anda.

Masalah dengan kode sampel yang Anda berikan adalah bahwa kelas yang Anda tulis memiliki ketergantungan konkret pada kelas ConcreteRepository. Kontainer IoC akan menghapus ketergantungan itu.

Stephen
sumber
22
Ini bukan keuntungan dari wadah IoC, mereka adalah keuntungan dari injeksi ketergantungan, yang dapat dilakukan dengan mudah dengan DI orang miskin
Ben Aaronson
Menulis kode DI yang baik tanpa wadah IoC sebenarnya bisa agak sulit. Ya, ada beberapa tumpang tindih dalam kelebihannya, tetapi ini semua kelebihan terbaik dieksploitasi dengan wadah IoC.
Stephen
Nah, dua alasan terakhir yang Anda tambahkan karena komentar saya lebih spesifik pada wadah dan keduanya merupakan argumen yang sangat kuat menurut pendapat saya
Ben Aaronson
"Hindari penggunaan baru" - juga menghalangi analisis kode statis, jadi Anda harus mulai menggunakan sesuatu seperti ini: hmemcpy.github.io/AgentMulder . Manfaat lain yang Anda jelaskan dalam paragraf ini adalah dengan DI dan bukan IOC. Juga kelas Anda masih akan digabungkan jika Anda tidak menggunakan baru tetapi akan menggunakan tipe konkret bukan antarmuka untuk parameter.
Den
1
Secara keseluruhan mengecat IoC sebagai sesuatu tanpa cacat, misalnya tidak disebutkan kelemahan besar: mendorong kelas kesalahan ke dalam runtime alih-alih waktu kompilasi.
Den
2

Menurut prinsip tanggung jawab tunggal, setiap kelas harus hanya memiliki satu tanggung jawab tunggal. Membuat instance kelas baru hanyalah tanggung jawab lain, jadi Anda harus merangkum kode semacam ini dalam satu atau beberapa kelas. Anda dapat melakukannya menggunakan pola kreasi apa saja, misalnya pabrik, pembangun, wadah DI dll ...

Ada prinsip-prinsip lain seperti inversi kontrol dan inversi ketergantungan. Dalam konteks ini mereka terkait dengan Instansiasi dependensi. Mereka menyatakan bahwa kelas tingkat tinggi harus dipisahkan dari kelas tingkat rendah (dependensi) yang mereka gunakan. Kita dapat memisahkan hal-hal dengan membuat antarmuka. Jadi kelas tingkat rendah harus mengimplementasikan antarmuka spesifik dan kelas tingkat tinggi harus memanfaatkan instance kelas yang mengimplementasikan antarmuka ini. (catatan: Kendala antarmuka seragam REST menerapkan pendekatan yang sama pada tingkat sistem.)

Kombinasi prinsip-prinsip ini dengan contoh (maaf untuk kode berkualitas rendah, saya menggunakan beberapa bahasa ad-hoc alih-alih C #, karena saya tidak tahu itu):

  1. Tidak ada SRP, tidak ada IoC

    class SomeHighLevelService
    {
        public doFooBar(){
            Crap crap = doFoo();
            doBar(crap);
        }
    
        public Crap doFoo(){
            //...
            return crap;
        }
    
        public doBar(Crap crap){
            //...
        }
    }
    
    SomeHighLevelService service = new SomeHighLevelService();
    service.doFooBar();
  2. Lebih dekat ke SRP, tidak ada IoC

    class SomeHighLevelService
    {
        public SomeHighLevelService(){
            Foo foo = new Foo();
            Bar bar = new Bar();
        }
    
        public doFooBar(){
            Crap crap = foo.doFoo();
            bar.doBar(crap);
        }
    }
    
    class Foo {
        public Crap doFoo(){
            //...
            return crap;
        }
    }
    
    class Bar {
        public doBar(Crap crap){
            //...
        }
    }
    
    SomeHighLevelService service = new SomeHighLevelService();
    service.doFooBar();
  3. Ya SRP, tidak ada IoC

    class HighLevelServiceProvider {
        public SomeHighLevelService getSomeHighLevelService(){
            SomeHighLevelService service = new SomeHighLevelService();
            service.setFoo(this.getFoo());
            service.getBar(this.getBar());
            return service;
        }
    
        private Foo getFoo(){
            return new Foo();
        }
    
        private Bar getBar(){
            return new Bar();
        }
    }
    
    class SomeHighLevelService
    {           
        public setFoo(Foo foo){
            this.foo = foo;
        }
    
        public setBar(Bar bar){
            this.bar = bar;
        }
    
        public doFooBar(){
            Crap crap = foo.doFoo();
            bar.doBar(crap);
        }
    
    }
    
    class Foo {
        public Crap doFoo(){
            //...
            return crap;
        }
    }
    
    class Bar {
        public doBar(Crap crap){
            //...
        }
    }
    
    HighLevelServiceProvider provider = new HighLevelServiceProvider();
    SomeHighLevelService service = provider.getSomeHighLevelService();
    service.doFooBar();
  4. Ya SRP, ya IOC

    interface HighLevelServiceProvider {
        SomeHighLevelService getSomeHighLevelService();
    }
    
    interface SomeHighLevelService {
        doFooBar();
    }
    
    interface Foo {
        Crap doFoo();
    }
    
    interface Bar {
        doBar(Crap crap);
    }
    
    
    class ConcreteHighLevelServiceContainer implements HighLevelServiceProvider {
        public SomeHighLevelService getSomeHighLevelService(){
            SomeHighLevelService service = new ConcreteHighLevelService();
            service.setFoo(this.getFoo());
            service.getBar(this.getBar());
            return service;
        }
    
        private Foo getFoo(){
            return new ConcreteFoo();
        }
    
        private Bar getBar(){
            return new ConcreteBar();
        }
    }
    
    class ConcreteHighLevelService implements SomeHighLevelService
    {           
        public setFoo(Foo foo){
            this.foo = foo;
        }
    
        public setBar(Bar bar){
            this.bar = bar;
        }
    
        public doFooBar(){
            Crap crap = foo.doFoo();
            bar.doBar(crap);
        }
    
    }
    
    class ConcreteFoo implements Foo {
        public Crap doFoo(){
            //...
            return crap;
        }
    }
    
    class ConcreteBar implements Bar {
        public doBar(Crap crap){
            //...
        }
    }
    
    
    HighLevelServiceProvider provider = new ConcreteHighLevelServiceContainer();
    SomeHighLevelService service = provider.getSomeHighLevelService();
    service.doFooBar();

Jadi kami akhirnya memiliki kode di mana Anda dapat mengganti setiap implementasi konkret dengan yang lain yang mengimplementasikan antarmuka ofc yang sama. Jadi ini bagus karena kelas yang berpartisipasi dipisahkan satu sama lain, mereka hanya tahu antarmuka. Keuntungan lain bahwa kode instantiation dapat digunakan kembali.

inf3rno
sumber