Bağımlılık Enjeksiyonu (DI) nedir?
Diğerlerinin söylediği gibi, Bağımlılık Enjeksiyonu (DI) , ilgi sınıfımızın (tüketici sınıfı) bağımlı olduğu diğer nesne örneklerinin ( UML anlamında ) doğrudan yaratılması ve ömrünün yönetilmesi sorumluluğunu ortadan kaldırır . Bu örnekler bunun yerine, tipik olarak yapıcı parametreleri olarak veya özellik ayarlayıcıları aracılığıyla tüketici sınıfımıza iletilir (tüketici nesnesinin başlatılması ve tüketici sınıfına geçirilmesi bağımlılığı yönetimi genellikle bir Denetimi Ters Çevirme (IoC) tarafından gerçekleştirilir kapsayıcısı , ancak bu başka bir konudur) .
DI, DIP ve KATI
Özellikle, Robert C Martin'in paradigma Tasarım Odaklı Nesne KATI ilkelerine , DI
olası uygulamalarından biridir Bağımlılık Inversion İlke (DIP) . DIP olduğu D
bir SOLID
mantra diğer DIP uygulamaları Servis Locator ve Eklenti desenleri - bulunur.
DIP amacı ise, bir ile elde edilebilir bir soyutlama vasıtasıyla bağlanması gevşetmek için, bunun yerine, sıkı ayrıştırarak sınıflar arasında somut bağımlılıklar ve etmek interface
, abstract class
ya dapure virtual class
kullanılan dil ve yaklaşım bağlı olarak.
DIP olmadan, kodumuz (bu 'tüketen sınıf' olarak adlandırdım) doğrudan somut bir bağımlılığa bağlıdır ve aynı zamanda bu bağımlılığın bir örneğini nasıl elde edeceğini ve yöneteceğini bilme sorumluluğuyla, yani kavramsal olarak:
"I need to create/use a Foo and invoke method `GetBar()`"
DIP'nin uygulanmasından sonra gereksinim gevşetilir ve Foo
bağımlılığın ömrünü elde etme ve yönetme endişesi ortadan kaldırılır:
"I need to invoke something which offers `GetBar()`"
Neden DIP (ve DI) kullanıyorsunuz?
Sınıflar arasındaki bağımlılıkları bu şekilde ayırmak , bu bağımlılık sınıflarının soyutlamanın önkoşullarını da yerine getiren diğer uygulamalarla kolayca değiştirilmesine izin verir (örneğin, bağımlılık aynı arayüzün başka bir uygulamasıyla değiştirilebilir). Ayrıca, diğerleri de gibi, olasılıkla DIP yoluyla Decouple sınıflarına en yaygın nedeni bir tüketen sınıf bu aynı bağımlılıklar artık stubbed edilebileceği gibi, izolasyon test edilmiş ve / veya alay edilecek sağlamaktır.
DI'nin bir sonucu, bağımlılık nesnesi örneklerinin ömür boyu yönetiminin artık bağımlılık nesnesi artık tüketen sınıfa aktarıldığı için (yapıcı veya ayarlayıcı enjeksiyonu yoluyla) tüketen bir sınıf tarafından kontrol edilmemesidir.
Bu farklı şekillerde görüntülenebilir:
- Tüketici sınıfı tarafından bağımlılıkların ömür boyu kontrolünün korunması gerekiyorsa, bağımlılık sınıfı örnekleri oluşturmak için (soyut) bir fabrika tüketici sınıfına enjekte edilerek kontrol yeniden oluşturulabilir. Tüketici, gerektiğinde
Create
fabrikada bir üzerinden örnek alabilecek ve bu örnekleri tamamlandığında imha edebilecektir .
- Veya, bağımlılık örneklerinin ömür boyu kontrolü bir IoC kapsayıcısından ayrılabilir (bunun hakkında daha fazla bilgi aşağıdadır).
DI ne zaman kullanılır?
- Eşdeğer bir uygulama için bir bağımlılığın yerine geçmesi gerektiğinde,
- Bir sınıfın yöntemlerini bağımlılıklarını izole ederek birim olarak test etmeniz gerektiğinde,
- Bir bağımlılığın yaşam süresinin belirsizliğinin denemeyi gerektirdiği durumlarda (örneğin Hey,
MyDepClass
iş parçacığı güvenlidir - ya bir tekton yaparsak ve aynı örneği tüm tüketicilere enjekte edersek?)
Misal
İşte basit bir C # uygulaması. Aşağıdaki Tüketici sınıfı göz önüne alındığında:
public class MyLogger
{
public void LogRecord(string somethingToLog)
{
Console.WriteLine("{0:HH:mm:ss} - {1}", DateTime.Now, somethingToLog);
}
}
Görünüşte zararsız olsa da, static
diğer iki sınıfa iki bağımlılığı vardır System.DateTime
ve System.Console
bu sadece günlük çıktı seçeneklerini sınırlamakla kalmaz (hiç kimse izlemiyorsa konsola giriş yapmak değersiz olacaktır), ancak daha da kötüsü, bağımlılık göz önüne alındığında otomatik olarak test edilmesi zordur. deterministik olmayan bir sistem saati.
Bununla birlikte DIP
, zaman damgasının bir bağımlılık olarak kaygısını ortadan kaldırarak ve MyLogger
sadece basit bir arayüzle eşleştirerek bu sınıfa başvurabiliriz :
public interface IClock
{
DateTime Now { get; }
}
Ayrıca Console
bir soyutlamaya olan bağımlılığı gevşetebiliriz TextWriter
. Bağımlılık Enjeksiyonu tipik olarak constructor
enjeksiyon (bir tüketen sınıfın yapıcısına bir parametre olarak bağımlılığa bir soyutlama geçirerek) veya Setter Injection
(bağımlılığı bir setXyz()
ayarlayıcı veya {set;}
tanımlanmış bir .Net Özelliği aracılığıyla geçirerek ) olarak uygulanır. Konstrüktör Enjeksiyonu tercih edilir, çünkü bu, inşaattan sonra sınıfın doğru durumda olacağını garanti eder ve iç bağımlılık alanlarının readonly
(C #) veya final
(Java) olarak işaretlenmesine izin verir . Yukarıdaki örnekte yapıcı enjeksiyonu kullanarak, bu bize aşağıdakileri bırakır:
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);
}
}
( Clock
Elbette geri dönebilecek bir betonun sağlanması DateTime.Now
ve iki bağımlılığın yapıcı enjeksiyonu yoluyla bir IoC konteyneri tarafından sağlanması gerekir)
Artık bağımlılıklar üzerinde zamana sahip olduğumuzdan ve yazılı çıktıyı gözetleyebileceğimizden, günlükçümüzün doğru çalıştığını kesin olarak kanıtlayan otomatik bir Birim Testi oluşturulabilir:
[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());
}
Sonraki adımlar
Bağımlılık enjeksiyonu, değişmez bir şekilde bir Inversion of Control konteyneri (IoC) ile , somut bağımlılık örneklerini enjekte etmek (sağlamak) ve kullanım ömrü örneklerini yönetmek için ilişkilendirilir. Yapılandırma / önyükleme işlemi sırasında IoC
kaplar aşağıdakilerin tanımlanmasına izin verir:
- her soyutlama ile yapılandırılmış somut uygulama arasında eşleme (ör. "bir tüketici her talep ettiğinde
IBar
, bir ConcreteBar
örnek döndür" )
- her bağımlılığın ömür boyu yönetimi için politikalar oluşturulabilir, örneğin her tüketici örneği için yeni bir nesne oluşturmak, tek bir bağımlılık örneğini tüm tüketiciler arasında paylaşmak, aynı bağımlılık örneğini yalnızca aynı iş parçacığında paylaşmak vb.
- .Net'te IoC kapsayıcıları , yapılandırılmış kullanım ömrü yönetimi doğrultusunda bağımlılıkların
IDisposable
sorumluluğunun bilincindedir ve bunlardan sorumlu olacaktır Disposing
.
Tipik olarak, IoC kapları yapılandırıldıktan / önyükleme yapıldıktan sonra arka planda sorunsuz bir şekilde çalışırlar, kodlayıcının bağımlılıklar hakkında endişelenmek yerine eldeki koda odaklanmasını sağlarlar.
DI-dostu kodun anahtarı, sınıfların statik olarak birleştirilmesinden kaçınmak ve Bağımlılıklar oluşturmak için new () kullanmamaktır
Yukarıdaki örneğe göre, bağımlılıkların ayrıştırılması bazı tasarım çabaları gerektirir ve geliştirici için, new
bağımlılıkları doğrudan kullanma alışkanlığını kırmak ve bunun yerine bağımlılıkları yönetmek için kaba güvenmek için gerekli bir paradigma değişimi vardır .
Ancak, özellikle ilgi sınıfınızı kapsamlı bir şekilde test etme yeteneğinde faydalar çoktur.
Not : new ..()
POCO / POJO / Serileştirme DTO'ları / Varlık Grafikleri / Anonim JSON projeksiyonları ve diğerlerinin oluşturulması / eşleştirilmesi / projeksiyonu (yoluyla ) - yani yöntemlerden kullanılan veya döndürülen "yalnızca veri" sınıfları veya kayıtları Bağımlılıklar olarak kabul edilmez ( UML anlamda) ve DI'ye tabi değildir. new
Bunları yansıtmak için kullanmak gayet iyi.