Mengapa seseorang menggunakan injeksi ketergantungan?

536

Saya mencoba memahami injeksi ketergantungan (DI), dan sekali lagi saya gagal. Sepertinya konyol. Kode saya tidak pernah berantakan; Saya jarang menulis fungsi dan antarmuka virtual (walaupun saya lakukan sekali dalam bulan biru) dan semua konfigurasi saya secara ajaib disambungkan ke dalam kelas menggunakan json.net (kadang-kadang menggunakan serializer XML).

Saya tidak begitu mengerti masalah apa yang dipecahkannya. Sepertinya cara untuk mengatakan: "Hai. Ketika Anda menjalankan fungsi ini, kembalikan objek yang jenis ini dan gunakan parameter / data ini."
Tapi ... mengapa saya menggunakannya? Catatan Saya tidak pernah perlu menggunakan objectjuga, tapi saya mengerti untuk apa itu.

Apa saja situasi nyata dalam membangun situs web atau aplikasi desktop di mana orang akan menggunakan DI? Saya dapat menemukan case dengan mudah mengapa seseorang mungkin ingin menggunakan antarmuka / fungsi virtual dalam sebuah game, tetapi sangat jarang (cukup langka sehingga saya tidak dapat mengingat satu contoh) untuk menggunakannya dalam kode non-game.

TJ Crowder
sumber
3
Ini mungkin juga informasi yang berguna: martinfowler.com/articles/injection.html
ta.speot.is
1
Lihat juga .
BlueRaja - Danny Pflughoeft
41
Saya dengan orang ini: jamesshore.com/Blog/Dependency-Injection-Demystified.html .
Eric Lippert
5
Penjelasan lain yang sangat sederhana dari DI: codearsenal.net/2015/03/...
ybonda

Jawaban:

840

Pertama, saya ingin menjelaskan asumsi yang saya buat untuk jawaban ini. Itu tidak selalu benar, tetapi cukup sering:

Antarmuka adalah kata sifat; kelas adalah kata benda.

(Sebenarnya, ada antarmuka yang juga merupakan kata benda, tapi saya ingin menggeneralisasi sini.)

Jadi, misalnya suatu antarmuka dapat berupa sesuatu seperti IDisposable, IEnumerableatau IPrintable. Kelas adalah implementasi aktual dari satu atau lebih antarmuka ini: Listatau Mapkeduanya bisa merupakan implementasi dari IEnumerable.

Untuk mendapatkan intinya: Seringkali kelas Anda bergantung satu sama lain. Misalnya Anda dapat memiliki Databasekelas yang mengakses database Anda (hah, kejutan! ;-)), tetapi Anda juga ingin kelas ini melakukan logging tentang mengakses database. Misalkan Anda memiliki kelas lain Logger, maka Databasememiliki ketergantungan pada Logger.

Sejauh ini bagus.

Anda bisa memodelkan ketergantungan ini di dalam Databasekelas Anda dengan baris berikut:

var logger = new Logger();

dan semuanya baik-baik saja. Tidak apa-apa sampai hari ketika Anda menyadari bahwa Anda memerlukan banyak penebang: Kadang-kadang Anda ingin masuk ke konsol, kadang-kadang ke sistem file, kadang-kadang menggunakan TCP / IP dan server logging jarak jauh, dan seterusnya ...

Dan tentu saja Anda TIDAK ingin mengubah semua kode Anda (sementara itu Anda memiliki gazillions) dan mengganti semua baris

var logger = new Logger();

oleh:

var logger = new TcpLogger();

Pertama, ini tidak menyenangkan. Kedua, ini rawan kesalahan. Ketiga, ini adalah pekerjaan yang bodoh dan berulang-ulang untuk monyet yang terlatih. Jadi apa yang kamu lakukan?

Jelas itu ide yang cukup bagus untuk memperkenalkan antarmuka ICanLog(atau serupa) yang diterapkan oleh semua berbagai penebang. Jadi langkah 1 dalam kode Anda adalah yang Anda lakukan:

ICanLog logger = new Logger();

Sekarang jenis inferensi tidak mengubah jenis lagi, Anda selalu memiliki satu antarmuka untuk dikembangkan. Langkah selanjutnya adalah Anda tidak ingin new Logger()berulang kali. Jadi, Anda menempatkan keandalan untuk membuat instance baru ke satu, kelas pabrik pusat, dan Anda mendapatkan kode seperti:

ICanLog logger = LoggerFactory.Create();

Pabrik itu sendiri memutuskan jenis logger apa yang akan dibuat. Kode Anda tidak peduli lagi, dan jika Anda ingin mengubah jenis logger yang digunakan, Anda mengubahnya sekali : Di dalam pabrik.

Sekarang, tentu saja, Anda dapat menggeneralisasi pabrik ini, dan membuatnya berfungsi untuk semua jenis:

ICanLog logger = TypeFactory.Create<ICanLog>();

Di suatu tempat TypeFactory ini membutuhkan data konfigurasi yang kelas aktual untuk instantiate ketika jenis antarmuka tertentu diminta, sehingga Anda memerlukan pemetaan. Tentu saja Anda dapat melakukan pemetaan ini di dalam kode Anda, tetapi kemudian perubahan jenis berarti kompilasi ulang. Tapi Anda juga bisa meletakkan pemetaan ini di dalam file XML, mis. Ini memungkinkan Anda untuk mengubah kelas yang sebenarnya digunakan bahkan setelah waktu kompilasi (!), Itu berarti secara dinamis, tanpa kompilasi ulang!

Untuk memberi Anda contoh yang berguna untuk ini: Pikirkan perangkat lunak yang tidak masuk secara normal, tetapi ketika pelanggan Anda menelepon dan meminta bantuan karena ia memiliki masalah, semua yang Anda kirim kepadanya adalah file konfigurasi XML yang diperbarui, dan sekarang ia memiliki logging diaktifkan, dan dukungan Anda dapat menggunakan file log untuk membantu pelanggan Anda.

Dan sekarang, ketika Anda mengganti nama sedikit, Anda berakhir dengan implementasi sederhana dari Service Locator , yang merupakan salah satu dari dua pola untuk Inversion of Control (karena Anda membalikkan kontrol atas siapa yang memutuskan kelas yang tepat untuk instantiate).

Semua ini mengurangi ketergantungan pada kode Anda, tetapi sekarang semua kode Anda memiliki ketergantungan pada pusat layanan tunggal.

Injeksi ketergantungan sekarang merupakan langkah berikutnya dalam baris ini: Cukup singkirkan ketergantungan tunggal ini ke pencari layanan: Daripada berbagai kelas yang meminta pencari layanan untuk implementasi untuk antarmuka tertentu, Anda - sekali lagi - kembalikan kontrol atas siapa yang memberi instantiasi apa .

Dengan injeksi dependensi, Databasekelas Anda sekarang memiliki konstruktor yang memerlukan parameter tipe ICanLog:

public Database(ICanLog logger) { ... }

Sekarang database Anda selalu memiliki logger untuk digunakan, tetapi tidak tahu lagi dari mana logger ini berasal.

Dan di sinilah kerangka kerja DI berperan: Anda mengkonfigurasi pemetaan Anda sekali lagi, dan kemudian meminta kerangka kerja DI Anda untuk instantiate aplikasi Anda untuk Anda. Karena Applicationkelas membutuhkan ICanPersistDataimplementasi, instance dari Databasedisuntikkan - tetapi untuk itu ia harus terlebih dahulu membuat instance dari jenis logger yang dikonfigurasi untuk ICanLog. Dan seterusnya ...

Jadi, untuk mempersingkat cerita: Injeksi ketergantungan adalah salah satu dari dua cara bagaimana menghapus dependensi dalam kode Anda. Ini sangat berguna untuk perubahan konfigurasi setelah waktu kompilasi, dan itu adalah hal yang hebat untuk pengujian unit (karena membuatnya sangat mudah untuk menyuntikkan bertopik dan / atau mengolok-olok).

Dalam praktiknya, ada hal-hal yang tidak dapat Anda lakukan tanpa pelacak layanan (misalnya, jika Anda tidak tahu sebelumnya berapa banyak instance yang Anda perlukan dari antarmuka tertentu: Kerangka kerja DI selalu menyuntikkan hanya satu instance per parameter, tetapi Anda dapat memanggil pelacak layanan di dalam satu lingkaran, tentu saja), maka paling sering setiap kerangka kerja DI juga menyediakan pencari layanan.

Tetapi pada dasarnya, itu saja.

PS: Apa yang saya jelaskan di sini adalah teknik yang disebut injeksi konstruktor , ada juga injeksi properti di mana bukan parameter konstruktor, tetapi properti sedang digunakan untuk mendefinisikan dan menyelesaikan dependensi. Pikirkan injeksi properti sebagai dependensi opsional, dan injeksi konstruktor sebagai dependensi wajib. Tetapi diskusi tentang hal ini berada di luar cakupan pertanyaan ini.

Golo Roden
sumber
7
Tentu saja Anda dapat melakukannya dengan cara ini juga, tetapi kemudian Anda harus mengimplementasikan logika ini di setiap kelas yang akan memberikan dukungan untuk pertukaran implementasi. Ini berarti banyak kode duplikat, redundan, dan ini juga berarti Anda perlu menyentuh kelas yang ada dan menulis ulang sebagian, setelah Anda memutuskan bahwa Anda membutuhkannya sekarang. DI memungkinkan Anda untuk menggunakan ini pada kelas sewenang-wenang, Anda tidak harus menuliskannya dengan cara khusus (kecuali mendefinisikan dependensi sebagai parameter dalam konstruktor).
Golo Roden
138
Inilah hal yang saya tidak pernah dapatkan tentang DI: itu membuat arsitekturnya jauh lebih rumit. Namun, seperti yang saya lihat, penggunaannya cukup terbatas. Contoh-contohnya tentu selalu sama: penebang dipertukarkan, model / akses data dipertukarkan. Terkadang tampilan saling dipertukarkan. Tapi begitulah. Apakah beberapa kasus ini benar-benar membenarkan arsitektur perangkat lunak yang jauh lebih kompleks? - Pengungkapan penuh: Saya sudah menggunakan DI untuk efek yang hebat, tapi itu untuk arsitektur plug-in yang sangat istimewa yang tidak akan saya generalisasikan.
Konrad Rudolph
17
@ GoloRoden, mengapa Anda memanggil antarmuka ICanLog bukannya ILogger? Saya bekerja dengan programmer lain yang sering melakukan ini, dan saya tidak pernah bisa memahami konvensi? Bagi saya itu seperti memanggil IEnumerable ICanEnumerate?
DermFrench
28
Saya menyebutnya ICanLog, karena kami sering bekerja dengan kata-kata (kata benda) yang tidak ada artinya. Misalnya, apa itu Pialang? Seorang Manajer? Bahkan Repositori tidak didefinisikan dengan cara yang unik. Dan memiliki semua hal ini sebagai kata benda adalah penyakit khas bahasa OO (lihat steve-yegge.blogspot.de/2006/03/… ). Yang ingin saya ungkapkan adalah bahwa saya memiliki komponen yang dapat melakukan logging untuk saya - jadi mengapa tidak menyebutnya seperti itu? Tentu saja, ini juga bermain dengan I sebagai orang pertama, maka ICanLog (ForYou).
Golo Roden
18
@David Unit testing bekerja dengan baik - setelah semua, unit tidak tergantung pada hal-hal lain (kalau tidak itu bukan unit). Apa yang tidak berfungsi tanpa wadah DI adalah pengujian tiruan. Cukup adil, saya tidak yakin bahwa manfaat dari mengejek melebihi kompleksitas tambahan menambahkan wadah DI dalam semua kasus. Saya melakukan pengujian unit yang ketat. Saya jarang melakukan ejekan.
Konrad Rudolph
499

Saya pikir banyak kali orang menjadi bingung tentang perbedaan antara injeksi ketergantungan dan kerangka kerja injeksi ketergantungan (atau wadah seperti yang sering disebut).

Injeksi ketergantungan adalah konsep yang sangat sederhana. Alih-alih kode ini:

public class A {
  private B b;

  public A() {
    this.b = new B(); // A *depends on* B
  }

  public void DoSomeStuff() {
    // Do something with B here
  }
}

public static void Main(string[] args) {
  A a = new A();
  a.DoSomeStuff();
}

Anda menulis kode seperti ini:

public class A {
  private B b;

  public A(B b) { // A now takes its dependencies as arguments
    this.b = b; // look ma, no "new"!
  }

  public void DoSomeStuff() {
    // Do something with B here
  }
}

public static void Main(string[] args) {
  B b = new B(); // B is constructed here instead
  A a = new A(b);
  a.DoSomeStuff();
}

Dan itu saja. Serius. Ini memberi Anda banyak keuntungan. Dua yang penting adalah kemampuan untuk mengontrol fungsionalitas dari tempat pusat ( Main()fungsi) alih-alih menyebarkannya di seluruh program Anda, dan kemampuan untuk lebih mudah menguji setiap kelas secara terpisah (karena Anda dapat memasukkan tiruan atau benda palsu lainnya ke dalam konstruktornya sebagai gantinya dari nilai nyata).

Kekurangannya, tentu saja, adalah bahwa Anda sekarang memiliki satu mega fungsi yang tahu tentang semua kelas yang digunakan oleh program Anda. Itulah yang dapat membantu kerangka kerja DI. Tetapi jika Anda mengalami kesulitan memahami mengapa pendekatan ini berharga, saya sarankan mulai dengan injeksi ketergantungan manual terlebih dahulu, sehingga Anda dapat lebih menghargai apa yang bisa dilakukan berbagai kerangka kerja untuk Anda.

Daniel Pryden
sumber
7
Mengapa saya lebih suka kode kedua daripada yang pertama? yang pertama hanya memiliki kata kunci baru, bagaimana ini membantu?
user962206
17
@ user962206 pikirkan tentang bagaimana Anda akan menguji A secara independen dari B
jk.
66
@ user962206, juga, pikirkan tentang apa yang akan terjadi jika B memerlukan beberapa parameter dalam konstruktornya: untuk membuat instantiate, A harus tahu tentang parameter-parameter itu, sesuatu yang mungkin sama sekali tidak terkait dengan A (hanya ingin bergantung pada B , bukan pada apa B tergantung pada). Melewati B yang sudah dibangun (atau subclass atau ejekan B dalam hal ini) ke konstruktor A memecahkan itu dan membuat A hanya bergantung pada B :)
epidemian
17
@ acidzombie24: Seperti banyak pola desain, DI tidak terlalu berguna kecuali basis kode Anda cukup besar untuk pendekatan sederhana menjadi masalah. Perasaan saya adalah bahwa DI tidak akan benar-benar menjadi perbaikan sampai aplikasi Anda memiliki lebih dari sekitar 20.000 baris kode, dan / atau lebih dari 20 dependensi pada pustaka atau kerangka kerja lain. Jika aplikasi Anda lebih kecil dari itu, Anda mungkin masih lebih suka memprogram dengan gaya DI, tetapi perbedaannya tidak akan sedramatis itu.
Daniel Pryden
2
@DanielPryden saya tidak berpikir ukuran kode penting seberapa dinamis kode Anda. jika Anda secara teratur menambahkan modul baru yang sesuai dengan antarmuka yang sama, Anda tidak perlu mengubah kode dependen sesering mungkin.
FistOfFury
35

Seperti jawaban lain yang dinyatakan, injeksi dependensi adalah cara untuk membuat dependensi Anda di luar kelas yang menggunakannya. Anda menyuntikkan mereka dari luar, dan mengambil kendali tentang ciptaan mereka dari dalam kelas Anda. Ini juga mengapa injeksi ketergantungan merupakan realisasi dari prinsip Inversion of control (IoC).

IoC adalah prinsipnya, di mana DI adalah polanya. Alasan bahwa Anda mungkin "membutuhkan lebih dari satu penebang" tidak pernah benar-benar bertemu, sejauh pengalaman saya, tetapi alasan sebenarnya adalah, bahwa Anda benar-benar membutuhkannya, setiap kali Anda menguji sesuatu. Sebuah contoh:

Fitur saya:

Ketika saya melihat penawaran, saya ingin menandai bahwa saya melihatnya secara otomatis, sehingga saya tidak lupa melakukannya.

Anda dapat menguji ini seperti ini:

[Test]
public void ShouldUpdateTimeStamp
{
    // Arrange
    var formdata = { . . . }

    // System under Test
    var weasel = new OfferWeasel();

    // Act
    var offer = weasel.Create(formdata)

    // Assert
    offer.LastUpdated.Should().Be(new DateTime(2013,01,13,13,01,0,0));
}

Jadi di suatu tempat di OfferWeasel, itu membuat Anda objek tawaran seperti ini:

public class OfferWeasel
{
    public Offer Create(Formdata formdata)
    {
        var offer = new Offer();
        offer.LastUpdated = DateTime.Now;
        return offer;
    }
}

Masalahnya di sini adalah, bahwa tes ini kemungkinan besar akan selalu gagal, karena tanggal yang ditetapkan akan berbeda dari tanggal yang ditetapkan, bahkan jika Anda hanya memasukkan DateTime.Nowkode tes itu mungkin dimatikan oleh beberapa milidetik dan karenanya akan selalu gagal. Solusi yang lebih baik sekarang adalah membuat antarmuka untuk ini, yang memungkinkan Anda untuk mengontrol waktu yang akan ditetapkan:

public interface IGotTheTime
{
    DateTime Now {get;}
}

public class CannedTime : IGotTheTime
{
    public DateTime Now {get; set;}
}

public class ActualTime : IGotTheTime
{
    public DateTime Now {get { return DateTime.Now; }}
}

public class OfferWeasel
{
    private readonly IGotTheTime _time;

    public OfferWeasel(IGotTheTime time)
    {
        _time = time;
    }

    public Offer Create(Formdata formdata)
    {
        var offer = new Offer();
        offer.LastUpdated = _time.Now;
        return offer;
    }
}

Antarmuka adalah abstraksi. Satu adalah hal yang NYATA, dan yang lain memungkinkan Anda untuk memalsukan waktu di mana diperlukan. Tes kemudian dapat diubah seperti ini:

[Test]
public void ShouldUpdateTimeStamp
{
    // Arrange
    var date = new DateTime(2013, 01, 13, 13, 01, 0, 0);
    var formdata = { . . . }

    var time = new CannedTime { Now = date };

    // System under test
    var weasel= new OfferWeasel(time);

    // Act
    var offer = weasel.Create(formdata)

    // Assert
    offer.LastUpdated.Should().Be(date);
}

Seperti ini, Anda menerapkan prinsip "inversi kontrol", dengan menyuntikkan dependensi (mendapatkan waktu sekarang). Alasan utama untuk melakukan ini adalah untuk pengujian unit terisolasi yang lebih mudah, ada cara lain untuk melakukannya. Sebagai contoh, sebuah antarmuka dan kelas di sini tidak perlu karena dalam fungsi C # dapat dilewatkan sebagai variabel, jadi alih-alih sebuah antarmuka Anda dapat menggunakan a Func<DateTime>untuk mencapai yang sama. Atau, jika Anda mengambil pendekatan dinamis, Anda hanya melewatkan objek apa pun yang memiliki metode setara ( bebek mengetik ), dan Anda tidak memerlukan antarmuka sama sekali.

Anda hampir tidak pernah membutuhkan lebih dari satu logger. Meskipun demikian, ketergantungan injeksi sangat penting untuk kode yang diketik secara statis seperti Java atau C #.

Dan ... Juga harus dicatat bahwa suatu objek hanya dapat memenuhi tujuannya dengan tepat pada saat runtime, jika semua dependensinya tersedia, sehingga tidak banyak digunakan dalam mengatur injeksi properti. Menurut pendapat saya, semua dependensi harus dipenuhi ketika konstruktor dipanggil, jadi konstruktor-injeksi adalah hal yang harus dilakukan.

Saya harap itu membantu.

penilai
sumber
4
Itu sebenarnya terlihat seperti solusi yang mengerikan. Saya pasti akan menulis kode lebih seperti saran Daniel Pryden menyarankan tetapi untuk tes unit khusus saya baru saja melakukan DateTime. Sekarang sebelum dan sesudah fungsi dan periksa apakah waktunya ada di antara? Menambahkan lebih banyak antarmuka / lebih banyak baris kode sepertinya ide yang buruk bagi saya.
3
Saya tidak suka contoh generik A (B) dan saya tidak pernah merasa seorang penebang diharuskan memiliki 100 implementasi. Ini adalah contoh yang baru-baru ini saya temui dan itu adalah salah satu dari 5 cara untuk menyelesaikannya, di mana orang benar-benar termasuk menggunakan PostSharp. Ini menggambarkan pendekatan injeksi ctor berbasis kelas klasik. Bisakah Anda memberikan contoh dunia nyata yang lebih baik di mana Anda menemukan penggunaan yang baik untuk DI?
pensiunan
2
Saya tidak pernah melihat penggunaan yang baik untuk DI. Itu sebabnya saya menulis pertanyaan.
2
Saya belum menemukannya bermanfaat. Kode saya selalu mudah dilakukan pengujian. Tampaknya DI baik untuk basis kode besar dengan kode buruk.
1
Perlu diketahui bahwa bahkan dalam program fungsional kecil, setiap kali Anda memiliki f (x), g (f) Anda sudah menggunakan injeksi dependensi, sehingga setiap kelanjutan dalam JS akan dihitung sebagai injeksi dependensi. Dugaan saya adalah bahwa Anda sudah menggunakannya;)
cessor
15

Saya pikir jawaban klasik adalah untuk membuat aplikasi yang lebih dipisahkan, yang tidak memiliki pengetahuan tentang implementasi yang akan digunakan selama runtime.

Misalnya, kami adalah penyedia pembayaran pusat, bekerja dengan banyak penyedia pembayaran di seluruh dunia. Namun, ketika ada permintaan, saya tidak tahu prosesor pembayaran mana yang akan saya hubungi. Saya bisa memprogram satu kelas dengan satu ton sakelar, seperti:

class PaymentProcessor{

    private String type;

    public PaymentProcessor(String type){
        this.type = type;
    }

    public void authorize(){
        if (type.equals(Consts.PAYPAL)){
            // Do this;
        }
        else if(type.equals(Consts.OTHER_PROCESSOR)){
            // Do that;
        }
    }
}

Sekarang bayangkan bahwa sekarang Anda harus mempertahankan semua kode ini dalam satu kelas karena tidak dipisahkan dengan benar, Anda dapat membayangkan bahwa untuk setiap prosesor baru yang Anda dukung, Anda harus membuat yang baru jika // beralih case untuk setiap metode, ini hanya menjadi lebih rumit, bagaimanapun, dengan menggunakan Dependency Injection (atau Inversion of Control - seperti yang kadang-kadang disebut, yang berarti bahwa siapa pun yang mengendalikan jalannya program hanya dikenal pada saat runtime, dan bukan komplikasi), Anda dapat mencapai sesuatu sangat rapi dan terawat.

class PaypalProcessor implements PaymentProcessor{

    public void authorize(){
        // Do PayPal authorization
    }
}

class OtherProcessor implements PaymentProcessor{

    public void authorize(){
        // Do other processor authorization
    }
}

class PaymentFactory{

    public static PaymentProcessor create(String type){

        switch(type){
            case Consts.PAYPAL;
                return new PaypalProcessor();

            case Consts.OTHER_PROCESSOR;
                return new OtherProcessor();
        }
    }
}

interface PaymentProcessor{
    void authorize();
}

** Kode tidak akan dikompilasi, saya tahu :)

Itai Sagi
sumber
+1 karena sepertinya Anda mengatakan Anda membutuhkannya di tempat Anda menggunakan metode / antarmuka virtual. Tapi itu masih jarang. Saya masih akan new ThatProcessor()menggunakannya sebagai
@Itai Anda dapat menghindari sakelar yang tak terhitung jumlahnya dengan pola desain pabrik kelas. Gunakan sistem refleksi. Refleksi. Perakitan. Dapatkan Eksekusi Perakitan (). CreateInstance ()
domenicr
@domenicr ofcourse! tapi saya ingin menjelaskannya dalam contoh sederhana
Itai Sagi
Saya setuju dengan penjelasan di atas, kecuali kebutuhan kelas pabrik. Saat kami menerapkan kelas pabrik hanya pilihan kasar. Penjelasan terbaik di atas yang saya temukan di Bab Poymorphism dan fungsi virtual oleh Bruce Erkel. DI yang benar harus bebas dari pemilihan dan jenis objek harus diputuskan pada saat run-time melalui antarmuka secara otomatis. Itulah juga perilaku polimorfik yang sebenarnya.
Arvind Krmar
Sebagai contoh (sesuai c ++) kami memiliki antarmuka umum yang hanya mengambil referensi ke kelas dasar dan mengimplementasikan perilaku kelas turunannya tanpa seleksi. void tune (Instrumen & i) {i.play (middleC); } int main () {Suling angin; tune (seruling); } Instrumen adalah kelas dasar, angin berasal darinya. Sesuai c ++, fungsi virtual memungkinkan untuk menerapkan perilaku kelas turunan melalui antarmuka umum.
Arvind Krmar
6

Alasan utama untuk menggunakan DI adalah karena Anda ingin menempatkan tanggung jawab atas pengetahuan implementasi di mana pengetahuan itu ada. Ide DI sangat sejalan dengan enkapsulasi dan desain oleh antarmuka. Jika ujung depan meminta dari belakang untuk beberapa data, maka apakah tidak penting untuk ujung depan bagaimana ujung belakang menyelesaikan pertanyaan itu. Terserah peminta permintaan.

Itu sudah umum di OOP untuk waktu yang lama. Sering kali membuat potongan kode seperti:

I_Dosomething x = new Impl_Dosomething();

Kekurangannya adalah bahwa kelas implementasi masih hardcoded, maka dari itu memiliki ujung depan pengetahuan yang implementasi digunakan. DI mengambil desain dengan antarmuka selangkah lebih maju, bahwa satu-satunya hal yang perlu diketahui ujung depan adalah pengetahuan tentang antarmuka. Di antara DYI dan DI adalah pola locator layanan, karena ujung depan harus menyediakan kunci (hadir dalam registri locator layanan) untuk memungkinkan permintaannya diselesaikan. Contoh pencari layanan:

I_Dosomething x = ServiceLocator.returnDoing(String pKey);

Contoh DI:

I_Dosomething x = DIContainer.returnThat();

Salah satu persyaratan DI adalah bahwa wadah harus dapat mengetahui kelas mana yang merupakan implementasi dari antarmuka mana. Oleh karena itu apakah wadah DI memerlukan desain yang sangat diketik dan hanya satu implementasi untuk setiap antarmuka pada saat yang sama. Jika Anda memerlukan lebih banyak implementasi antarmuka pada saat yang sama (seperti kalkulator), Anda memerlukan pencari lokasi atau pola desain pabrik.

D (b) I: Injeksi Ketergantungan dan Desain berdasarkan Antarmuka. Pembatasan ini bukan masalah praktis yang sangat besar. Manfaat menggunakan D (b) I adalah melayani komunikasi antara klien dan penyedia. Antarmuka adalah perspektif pada suatu objek atau serangkaian perilaku. Yang terakhir ini sangat penting di sini.

Saya lebih suka administrasi kontrak layanan bersama dengan D (b) I dalam pengkodean. Mereka harus pergi bersama. Penggunaan D (b) I sebagai solusi teknis tanpa administrasi organisasi dari kontrak layanan tidak terlalu menguntungkan menurut saya, karena DI kemudian hanyalah lapisan enkapsulasi tambahan. Tetapi ketika Anda dapat menggunakannya bersama-sama dengan administrasi organisasi Anda benar-benar dapat menggunakan prinsip pengorganisasian D (b) yang saya tawarkan. Ini dapat membantu Anda dalam jangka panjang untuk menyusun komunikasi dengan klien dan departemen teknis lainnya dalam topik-topik seperti pengujian, versi, dan pengembangan alternatif. Ketika Anda memiliki antarmuka implisit seperti dalam kelas hardcoded, maka itu jauh lebih sedikit dari waktu ke waktu kemudian ketika Anda membuatnya eksplisit menggunakan D (b) I. Itu semua bermuara pada pemeliharaan, yang dari waktu ke waktu dan tidak pada suatu waktu. :-)

Loek Bergman
sumber
1
"Kelemahannya adalah kelas implementasi masih hardcoded" <- sebagian besar waktu hanya ada satu implementasi dan seperti saya katakan saya tidak bisa memikirkan kode nongame yang membutuhkan antarmuka yang belum dibangun di (.NET ).
@ acidzombie24 Mungkin ... tetapi bandingkan upaya untuk mengimplementasikan solusi menggunakan DI dari awal hingga upaya mengubah solusi non-DI nanti jika Anda membutuhkan antarmuka. Saya hampir selalu memilih opsi pertama. Lebih baik membayar sekarang $ 100 daripada harus membayar $ 100.000 besok.
Golo Roden
1
@GoloRoden Memang, pemeliharaan adalah masalah utama menggunakan teknik seperti D (b) I. Itu adalah 80% dari biaya aplikasi. Sebuah desain di mana perilaku yang diperlukan dibuat eksplisit menggunakan antarmuka dari awal menghemat banyak waktu dan uang bagi organisasi.
Loek Bergman
Saya tidak akan mengerti sampai saya harus membayarnya karena sejauh ini saya membayar $ 0 dan sejauh ini saya hanya perlu membayar $ 0. Tapi saya membayar $ 0,05 untuk menjaga setiap baris atau fungsi tetap bersih.