Apa itu Dependency Injection (DI)?
Seperti yang orang lain katakan, Dependency Injection (DI) menghilangkan tanggung jawab penciptaan langsung, dan manajemen masa hidup, dari instance objek lain yang menjadi dasar ketergantungan kelas kelas kami (kelas konsumen) (dalam arti UML ). Contoh-contoh ini bukannya diteruskan ke kelas konsumen kami, biasanya sebagai parameter konstruktor atau melalui setter properti (pengelolaan objek dependensi yang mem-instans dan meneruskan ke kelas konsumen biasanya dilakukan oleh Inversion of Control (IoC) , tapi itu topik lain) .
DI, DIP dan SOLID
Secara khusus, dalam paradigma prinsip SOLID Obyek Berorientasi Desain Robert C Martin , DI
adalah salah satu implementasi yang mungkin dari Dependency Inversion Principle (DIP) . The DIP adalah D
dari SOLID
mantra - implementasi DIP lainnya meliputi Jasa Locator, dan pola Plugin.
Tujuan dari DIP adalah untuk memisahkan ketergantungan yang kuat dan konkret antar kelas, dan sebagai gantinya, melonggarkan kopling melalui abstraksi, yang dapat dicapai melalui interface
, abstract class
ataupure virtual class
, tergantung pada bahasa dan pendekatan yang digunakan.
Tanpa DIP, kode kami (saya menyebutnya 'kelas konsumsi') secara langsung digabungkan ke ketergantungan konkret dan juga sering dibebani dengan tanggung jawab untuk mengetahui cara mendapatkan, dan mengelola, contoh ketergantungan ini, yaitu secara konseptual:
"I need to create/use a Foo and invoke method `GetBar()`"
Sedangkan setelah penerapan DIP, persyaratan dilonggarkan, dan kekhawatiran untuk mendapatkan dan mengelola umur Foo
ketergantungan telah dihapus:
"I need to invoke something which offers `GetBar()`"
Mengapa menggunakan DIP (dan DI)?
Decoupling dependensi antara kelas-kelas dengan cara ini memungkinkan substitusi yang mudah dari kelas-kelas dependensi ini dengan implementasi lain yang juga memenuhi prasyarat dari abstraksi (misalnya ketergantungan dapat diubah dengan implementasi lain dari antarmuka yang sama). Selain itu, yang lain telah disebutkan, mungkin yang alasan yang paling umum untuk kelas decouple melalui DIP adalah untuk memungkinkan kelas mengkonsumsi untuk diuji dalam isolasi, karena ini dependensi yang sama sekarang dapat mematikan dan / atau mengejek.
Salah satu konsekuensi dari DI adalah manajemen umur instance objek dependensi tidak lagi dikendalikan oleh kelas konsumsi, karena objek dependensi kini diteruskan ke kelas konsumsi (melalui konstruktor atau injeksi setter).
Ini dapat dilihat dengan berbagai cara:
- Jika kontrol umur pakai dari ketergantungan oleh kelas konsumsi perlu dipertahankan, kontrol dapat didirikan kembali dengan menyuntikkan pabrik (abstrak) untuk membuat instance kelas ketergantungan, ke dalam kelas konsumen. Konsumen akan dapat memperoleh mesin virtual melalui
Create
pada pabrik sesuai kebutuhan, dan membuang mesin virtual tersebut setelah selesai.
- Atau, kontrol rentang waktu kejadian dependensi dapat dilepaskan ke wadah IoC (lebih lanjut tentang ini di bawah).
Kapan harus menggunakan DI?
- Di mana ada kemungkinan akan perlu menggantikan ketergantungan untuk implementasi yang setara,
- Kapan pun Anda perlu menguji unit metode dalam isolasi dependensinya,
- Di mana ketidakpastian rentang usia ketergantungan dapat menjamin eksperimen (mis. Hei,
MyDepClass
apakah thread aman - bagaimana jika kita menjadikannya tunggal dan menyuntikkan contoh yang sama ke semua konsumen?)
Contoh
Berikut ini adalah implementasi C # yang sederhana. Diberikan kelas Konsumsi di bawah ini:
public class MyLogger
{
public void LogRecord(string somethingToLog)
{
Console.WriteLine("{0:HH:mm:ss} - {1}", DateTime.Now, somethingToLog);
}
}
Meskipun tampaknya tidak berbahaya, ia memiliki dua static
dependensi pada dua kelas lain, System.DateTime
dan System.Console
, yang tidak hanya membatasi opsi keluaran logging (masuk ke konsol tidak akan bernilai jika tidak ada yang menonton), tetapi lebih buruk lagi, sulit untuk secara otomatis menguji mengingat ketergantungan pada jam sistem non-deterministik.
Namun kita dapat menerapkan DIP
ke kelas ini, dengan mengabstraksi keprihatinan timestamping sebagai ketergantungan, dan MyLogger
hanya menyambung ke antarmuka sederhana:
public interface IClock
{
DateTime Now { get; }
}
Kami juga dapat melonggarkan ketergantungan pada Console
abstraksi, seperti a TextWriter
. Dependency Injection biasanya diimplementasikan sebagai constructor
injeksi (meneruskan abstraksi ke dependensi sebagai parameter ke konstruktor dari kelas konsumsi) atau Setter Injection
(melewati ketergantungan melalui setXyz()
setter atau .Net Properti dengan yang {set;}
ditentukan). Injeksi Konstruktor lebih disukai, karena ini menjamin kelas akan berada dalam keadaan yang benar setelah konstruksi, dan memungkinkan bidang dependensi internal ditandai sebagai readonly
(C #) atau final
(Jawa). Jadi menggunakan injeksi konstruktor pada contoh di atas, ini memberi kita:
public class MyLogger : ILogger // Others will depend on our logger.
{
private readonly TextWriter _output;
private readonly IClock _clock;
// Dependencies are injected through the constructor
public MyLogger(TextWriter stream, IClock clock)
{
_output = stream;
_clock = clock;
}
public void LogRecord(string somethingToLog)
{
// We can now use our dependencies through the abstraction
// and without knowledge of the lifespans of the dependencies
_output.Write("{0:yyyy-MM-dd HH:mm:ss} - {1}", _clock.Now, somethingToLog);
}
}
(Beton Clock
perlu disediakan, yang tentu saja dapat dikembalikan DateTime.Now
, dan dua dependensi perlu disediakan oleh wadah IoC melalui injeksi konstruktor)
Unit Test otomatis dapat dibangun, yang secara definitif membuktikan bahwa logger kami berfungsi dengan benar, karena kami sekarang memiliki kendali atas dependensi - waktu, dan kami dapat memata-matai hasil tertulis:
[Test]
public void LoggingMustRecordAllInformationAndStampTheTime()
{
// Arrange
var mockClock = new Mock<IClock>();
mockClock.Setup(c => c.Now).Returns(new DateTime(2015, 4, 11, 12, 31, 45));
var fakeConsole = new StringWriter();
// Act
new MyLogger(fakeConsole, mockClock.Object)
.LogRecord("Foo");
// Assert
Assert.AreEqual("2015-04-11 12:31:45 - Foo", fakeConsole.ToString());
}
Langkah selanjutnya
Injeksi ketergantungan selalu terkait dengan wadah Inversion of Control (IoC) , untuk menyuntikkan (menyediakan) contoh ketergantungan beton, dan untuk mengelola contoh umur pakai. Selama proses konfigurasi / bootstrap, IoC
wadah memungkinkan yang berikut ini untuk didefinisikan:
- pemetaan antara setiap abstraksi dan implementasi konkret yang dikonfigurasi (mis. "setiap kali konsumen meminta
IBar
, kembalikan ConcreteBar
contoh" )
- kebijakan dapat diatur untuk manajemen masa pakai masing-masing ketergantungan, misalnya untuk membuat objek baru untuk setiap instance konsumen, untuk berbagi contoh ketergantungan tunggal di semua konsumen, untuk berbagi contoh ketergantungan yang sama hanya di seluruh thread yang sama, dll.
- Di .Net, wadah IoC mengetahui protokol seperti
IDisposable
dan akan mengambil tanggung jawab Disposing
dependensi sejalan dengan manajemen masa pakai yang dikonfigurasi.
Biasanya, begitu wadah IoC telah dikonfigurasi / bootstrap, mereka beroperasi dengan mulus di latar belakang yang memungkinkan koder untuk fokus pada kode yang ada daripada khawatir tentang ketergantungan.
Kunci kode DI-friendly adalah untuk menghindari kopling statis kelas, dan tidak menggunakan yang baru () untuk pembuatan Dependensi
Seperti contoh di atas, decoupling of dependency memang memerlukan beberapa upaya desain, dan bagi pengembang, ada perubahan paradigma yang diperlukan untuk menghentikan kebiasaan new
ketergantungan secara langsung, dan sebaliknya mempercayai wadah untuk mengelola dependensi.
Tetapi manfaatnya banyak, terutama dalam kemampuan untuk menguji kelas minat Anda secara menyeluruh.
Catatan : Pembuatan / pemetaan / proyeksi (via new ..()
) POCO / POJO / Serialisasi DTO / Grafik Entitas / Proyeksi JSON anonim dkk - yaitu kelas atau catatan "Hanya data" - digunakan atau dikembalikan dari metode tidak dianggap sebagai Dependensi (dalam UML sense) dan tidak tunduk pada DI. Menggunakan new
untuk memproyeksikan ini baik-baik saja.