Apa itu injeksi konstruktor?

47

Saya telah melihat istilah injeksi konstruktor dan injeksi ketergantungan saat membaca artikel tentang pola desain (Layanan locator).

Ketika saya mencari Google di injeksi konstruktor, saya mendapat hasil yang tidak jelas, yang mendorong saya untuk check in di sini.

Apa itu injeksi konstruktor? Apakah ini tipe injeksi ketergantungan khusus? Contoh kanonik akan sangat membantu!

Sunting

Meninjau kembali pertanyaan-pertanyaan ini setelah jeda seminggu, saya dapat melihat betapa tersesatnya saya ... Kalau-kalau ada orang lain muncul di sini, saya akan memperbarui tubuh pertanyaan dengan sedikit belajar dari saya. Silahkan berkomentar / koreksi. Injeksi konstruktor dan injeksi properti adalah dua jenis Injeksi Ketergantungan.

TheSilverBullet
sumber
5
Hit pertama di google jelas menjelaskannya ... misko.hevery.com/2009/02/19/…
user281377

Jawaban:

95

Saya bukan ahli, tapi saya pikir saya bisa membantu. Dan ya, itu adalah tipe Injeksi Ketergantungan khusus.

Penafian: Hampir semua ini "dicuri" dari Ninject Wiki

Mari kita periksa ide injeksi ketergantungan dengan berjalan melalui contoh sederhana. Katakanlah Anda sedang menulis game blockbuster berikutnya, di mana para prajurit bangsawan melakukan pertempuran untuk kemuliaan besar. Pertama, kita akan membutuhkan senjata yang cocok untuk mempersenjatai prajurit kita.

class Sword 
{
    public void Hit(string target)
    {
        Console.WriteLine("Chopped {0} clean in half", target);
    }
}

Lalu, mari kita buat kelas untuk mewakili prajurit kita sendiri. Untuk menyerang lawannya, prajurit akan membutuhkan metode Serangan (). Ketika metode ini dipanggil, ia harus menggunakan Pedangnya untuk menyerang lawannya.

class Samurai
{
    readonly Sword sword;
    public Samurai() 
    {
        this.sword = new Sword();
    }

    public void Attack(string target)
    {
        this.sword.Hit(target);
    }
}

Sekarang, kita bisa membuat Samurai kita dan melakukan pertempuran!

class Program
{
    public static void Main() 
    {
        var warrior = new Samurai();
        warrior.Attack("the evildoers");
    }
}

Seperti yang mungkin Anda bayangkan, ini akan mencetak Cincang para penjahat bersih setengah menjadi konsol. Ini bekerja dengan baik, tetapi bagaimana jika kita ingin mempersenjatai Samurai kita dengan senjata lain? Karena Pedang dibuat di dalam konstruktor kelas Samurai, kita harus memodifikasi implementasi kelas untuk membuat perubahan ini.

Ketika suatu kelas tergantung pada ketergantungan konkret, itu dikatakan tergabung erat dengan kelas itu . Dalam contoh ini, kelas Samurai sangat erat dengan kelas Pedang. Ketika kelas-kelas digabungkan secara ketat, mereka tidak dapat dipertukarkan tanpa mengubah implementasinya. Untuk menghindari kelas kopling ketat, kita dapat menggunakan antarmuka untuk memberikan tingkat tipuan. Mari kita buat antarmuka untuk mewakili senjata di gim kita.

interface IWeapon
{
    void Hit(string target);
}

Kemudian, kelas Sword kami dapat mengimplementasikan antarmuka ini:

class Sword : IWeapon
{
    public void Hit(string target) 
    {
        Console.WriteLine("Chopped {0} clean in half", target);
    }
}

Dan kita dapat mengubah kelas Samurai kita:

class Samurai
{
    readonly IWeapon weapon;
    public Samurai() 
    {
        this.weapon = new Sword();
    }
    public void Attack(string target) 
    {
        this.weapon.Hit(target);
    }
}

Sekarang Samurai kita dapat dipersenjatai dengan senjata yang berbeda. Tapi tunggu! Pedang masih dibuat di dalam konstruktor Samurai. Karena kita masih perlu mengubah implementasi Samurai untuk memberikan prajurit kita senjata lain, Samurai masih tergabung erat dengan Sword.

Untungnya, ada solusi mudah. Daripada menciptakan Pedang dari dalam konstruktor Samurai, kita dapat mengeksposnya sebagai parameter konstruktor sebagai gantinya. Juga dikenal sebagai Injeksi Konstruktor.

class Samurai
{
    readonly IWeapon weapon;
    public Samurai(IWeapon weapon) 
    {
        this.weapon = weapon;
    }
    public void Attack(string target) 
    {
        this.weapon.Hit(target);
    }
}

Seperti yang ditunjukkan Giorgio, ada juga injeksi properti. Itu akan menjadi sesuatu seperti:

class Samurai
{
    IWeapon weapon;

    public Samurai() { }


    public void SetWeapon(IWeapon weapon)
    {
        this.weapon = weapon;
    }

    public void Attack(string target) 
    {
        this.weapon.Hit(target);
    }

}

Semoga ini membantu.

Luiz Angelo
sumber
8
Saya suka yang ini! :) Itu benar-benar membaca seperti cerita! Hanya penasaran. Jika Anda ingin mempersenjatai Samurai yang sama dengan beberapa senjata (berurutan), injeksi properti akan menjadi cara terbaik bukan?
TheSilverBullet
Ya, Anda bisa melakukannya. Sebenarnya, saya pikir Anda akan lebih baik melayani melewati IWeapon sebagai parameter untuk metode Serangan. Seperti "Public void Attack (IWeapon with, target string)". Dan untuk menghindari duplikat kode saya akan membuat metode yang ada "public void Attack (target string)" tetap utuh dan membuatnya memanggil "this.Attack (this.weapon, target)".
Luiz Angelo
Dan kemudian dikombinasikan dengan pabrik, Anda bisa mendapatkan metode seperti CreateSwordSamurai ()! Contoh yang bagus.
Geerten
2
Contoh yang bagus! Sangat jelas ...
Serge van den Oever
@ Luiz Angelo. Maaf, saya tidak yakin saya mengerti komentar Anda: "Sebenarnya, saya pikir Anda akan lebih baik melayani melewati IWeapon sebagai parameter untuk metode Serangan. Seperti ..". Maksudmu kelebihan metode Serangan di kelas Samurai?
Pap
3

Saya akan berusaha semaksimal mungkin untuk membuat ini sangat dasar. Dengan begitu, Anda dapat membangun konsep dan membuat solusi atau ide kompleks Anda sendiri.

Sekarang bayangkan kita memiliki sebuah gereja bernama Jubilee, yang memiliki cabang di seluruh dunia. Tujuan kami adalah mendapatkan item dari setiap cabang. Ini akan menjadi solusi dengan DI;

1) Buat antarmuka IJubilee:

public interface IJubilee
{
    string GetItem(string userInput);
}

2) Buat JubileeDI kelas yang akan menggunakan antarmuka IJubilee sebagai konstruktor dan mengembalikan item:

public class JubileeDI
{
    readonly IJubilee jubilee;

    public JubileeDI(IJubilee jubilee)
    {
        this.jubilee = jubilee;
    }

    public string GetItem(string userInput)
    {
        return this.jubilee.GetItem(userInput);
    }
}

3) Sekarang buat tiga brache dari Jubilee yaitu JubileeGT, JubileeHOG, JubileeCOV, yang semuanya HARUS mewarisi antarmuka IJubilee. Untuk bersenang-senang, buat salah satu dari mereka mengimplementasikan metodenya sebagai virtual:

public class JubileeGT : IJubilee
{
    public virtual string GetItem(string userInput)
    {
        return string.Format("For JubileeGT, you entered {0}", userInput);
    }
}

public class JubileeHOG : IJubilee
{
    public string GetItem(string userInput)
    {
        return string.Format("For JubileeHOG, you entered {0}", userInput);
    }
}

public class JubileeCOV : IJubilee
{
    public string GetItem(string userInput)
    {
        return string.Format("For JubileCOV, you entered {0}", userInput);
    }
}

public class JubileeGTBranchA : JubileeGT
{
    public override string GetItem(string userInput)
    {
        return string.Format("For JubileeGT branch A, you entered {0}", userInput);
    }
}

4) Itu dia! Sekarang mari kita lihat DI beraksi:

        JubileeDI jCOV = new JubileeDI(new JubileeCOV());
        JubileeDI jHOG = new JubileeDI(new JubileeHOG());
        JubileeDI jGT = new JubileeDI(new JubileeGT());
        JubileeDI jGTA = new JubileeDI(new JubileeGTBranchA());

        var item = jCOV.GetItem("Give me some money!");
        var item2 = jHOG.GetItem("Give me a check!");
        var item3 = jGT.GetItem("I need to be fed!!!");
        var item4 = jGTA.GetItem("Thank you!");

Untuk setiap instance dari kelas kontainer, kami melewati instance baru dari kelas yang kami butuhkan, yang memungkinkan kopling longgar.

Semoga ini menjelaskan konsepnya secara singkat.

David Grant
sumber
2

Misalkan Anda memiliki kelas Asetiap instance yang membutuhkan instance dari kelas lain B.

class A
{
   B b;
}

Ketergantungan injeksi berarti bahwa referensi ke Bdiatur oleh objek yang mengelola turunan A(sebagai lawan memiliki kelas yang Amengelola referensi Bsecara langsung).

Injeksi konstruktor berarti bahwa referensi untuk Bditeruskan sebagai parameter untuk konstruktor Adan ditetapkan dalam konstruktor:

class A
{
    B b;

    A(B b)
    {
        this.b = b;
    }
}

Alternatifnya adalah menggunakan metode setter (atau properti) untuk mengatur referensi B. Dalam hal ini, objek yang mengelola sebuah contoh adari Akeharusan pertama memanggil konstruktor dari Adan kemudian memanggil metode setter untuk mengatur variabel anggota A.bsebelum adigunakan. Metode injeksi terakhir ini diperlukan ketika benda berisi referensi siklik satu sama lain, misalnya jika sebuah contoh bdari Byang diatur dalam sebuah contoh adari Aberisi referensi kembali ke a.

** SUNTING**

Berikut adalah beberapa detail untuk menanggapi komentar.

1. Kelas A mengelola instance B

class A
{
    B b;

    A()
    {
        b = new B();
    }
 }

2. B instance diatur dalam konstruktor

class A
{
    B b;

    A(B b)
    {
        this.b = b;
    }
}

class M
{
    ...
    B b = new B();
    A a = new A(b);
}

3. B instance diatur menggunakan metode setter

class A
{
    B b;

    A()
    {
    }

    void setB(B b)
    {
        this.b = b;
    }
}

class M
{
    ...
    B b = new B();
    A a = new A();

    a.setB(b);
}
Giorgio
sumber
... sebagai lawan memiliki kelas A mengelola referensi ke B secara langsung . Apa artinya ini? Bagaimana Anda melakukannya dalam kode? (Maafkan ketidaktahuan saya.)
TheSilverBullet
1
Contoh samurai jauh lebih baik. Mari kita semua berhenti menggunakan huruf untuk variabel, silakan ...
stuartdotnet
@stuartdotnet: Saya tidak membantah tentang contoh samurai yang lebih baik dari yang lebih buruk, tetapi mengapa berhenti menggunakan variabel satu huruf? Jika variabel tidak memiliki makna tertentu tetapi hanya placeholder, variabel huruf tunggal jauh lebih kompak dan to the point. Menggunakan nama yang lebih panjang seperti itemAatau objectAhanya untuk membuat nama lebih lama tidak selalu lebih baik.
Giorgio
1
Maka Anda telah melewatkan poin saya - menggunakan nama yang tidak deskriptif sulit untuk dibaca, diikuti dan dipahami, dan bukan cara yang baik untuk menjelaskan suatu hal
stuartdotnet
@stuartdotnet: Saya rasa saya tidak melewatkan poin Anda: Anda mengklaim bahwa nama deskriptif selalu lebih baik dan membuat kode selalu lebih mudah dibaca dan menjelaskan sendiri.
Giorgio