Merhaba bu yazımızda sizlerle C# .Net ile Dependency Injection konusunu ele alacağız.Dependency Injection ; Nesneye dayalı programlama metodolojisinde önemli bir yeri olan gevşek bağlı (loosely coupled) yazılım tasarım metodunun bir uygulanış yöntemidir. Sınıfların ve katmanların olabildiğince birbirine gevşek bağlı olması ileride proje için hayat kurtarıcı bir öneme sahiptir. Gevşek bağlanmayan doğrudan düz kod antipattern veya yanlış oop yazılım dizaynı ile yapılan projelerde, proje büyüdükçe veya bittikten sonra gelecek olan bir veya birkaç değişiklik projeyi çöpe çevirebilir veyahut içinden çıkılmaz anlaşılamaz bir kod yığını, kod yazılmaya korkulan bir canavara dönüştürür. Bu yüzden her zaman tasarımlarımızı YAGNI prensibinden geçirirken tasarım gevşek olacak ama süzgeç gevşek olmayacak 🙂 Yani gerekli bir ihtiyacı tutup en baştan elemeyeceğiz, YAGNI prensibine uydum demeyeceğiz. Lafı daha çok dolandırmadan örnek uygulama ile devam edelim.
Farzedelim ki bir gezi firmasına yazılım geliştiriyorsunuz. Firma araçla beraber şoför kiralayan bir organizator ile çalışıyor. İlk olarak sınıf tasarımlarımızı aşağıdaki yapalım.Sonra doğru ve yanlış olan tasarımlardan bahsedelim.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
public interface IDriver { void Drive(); } // ARABA İLE GEZME İSTEĞİ GELDİĞİNDE EKLENDİ class CarDriver : IDriver { public void Drive() { Console.WriteLine("araba sürülüyor"); } } class BusDriver : IDriver { public void Drive() { Console.WriteLine("Otobüs sürülüyor"); } } public interface IVehicle { void Passenger(); } class BusPassenger : IVehicle { public void Passenger() { Console.WriteLine("Otobüs ile geziliyor"); } } // ARABA İLE GEZME İSTEĞİ GELDİĞİNDE EKLENDİ class CarPassenger : IVehicle { public void Passenger() { Console.WriteLine("Araba ile geziliyor"); } } |
Yukarıdaki tasarımımız doğru olan sınıf ve interface tanımlamalarıdır. Şimdi bu sınıfları kullanarak organizasyonu yönetecek olan “Organizator” sınıfı yanlış olarak yazalım. Bakalım yanlış nasılmış ??
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Organizator_Yanlis { public Organizator_Yanlis() { } public void MakeTrip() { BusDriver busdriver = new BusDriver(); busdriver.Drive(); BusPassenger busPassenger = new BusPassenger(); busPassenger.Passenger(); } } |
Yanlış “Organizator” aşağıdaki kodlar ile Program.cs den çağıralım
1 2 3 4 5 6 7 8 9 |
public static class Program { static void Main() { Organizator_Yanlis organizator = new Organizator_Yanlis(); organizator.MakeTrip(); } } |
Şimdi tam burada gezi firmasına araba ile gezi yapmak isteyen bir müşteri talebi geldi. Firma sizi aradı ve artık araba ile gezi organizasyonu kiralayabilmek istediğini söyledi. Eski tasarımınızda bulunan aşağıdaki kodları yorum satırları ile kapatıp yerine CarDriver,CarPassenger sınıflarını tüketmeyi düşünebilirsiniz.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//BusDriver busdriver = new BusDriver(); //busdriver.Drive(); //BusPassenger busPassenger = new BusPassenger(); //busPassenger.Passenger(); CarDriver cardriver = new CarDriver(); cardriver.Drive(); CarPassenger carPassenger = new CarPassenger(); carPassenger.Passenger(); |
Böyle yaptığınızda otobüsle gezi yapmayı kapatmış oldunuz. Tasarıma organizator tipini belirtecek ilaveler yapabilirsiniz. If blokları ile organizator tipine göre nesne örnekleri türetebilirsiniz. Yine de bu sorununuzu çözmeyecektir. Yorum satırından kurtuldunuz fakat kodun tek yönlü işlevsizliğinden kurtulamadınız.
Peki nedir doğru olan “Organizator” sınıfı tasarımı ? İşte alt satırda
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class Organizator { IDriver _driver = null; IVehicle _vehicle = null; public Organizator(IDriver driver, IVehicle vehicle) { _driver = driver; _vehicle = vehicle; } public void MakeTrip() { _vehicle.Passenger(); _driver.Drive(); } } |
Şimdi bu kodu tüketecek olan program.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public static class Program { static void Main() { Organizator organizator = new Organizator(new BusDriver(), new BusPassenger()); organizator.MakeTrip(); // aynı anda birden çok organizasyon desteği Organizator organizator = new Organizator(new CarDriver(), new CarPassenger()); organizator.MakeTrip(); } } |
Evet işte gördüğünüz üzere 2 organizasyon yöntemini de if bloğu yada yorum satırına ihtiyaç duymadan destekleyebildik. Tasarımımız artık genişleyebilir ve kolay ,esnek ,yönetilebilir,kolay okunabilir şekilde , e daha ne isteyelim 🙂 Yeni ihtiyac doğduğunda IDriver ve IVehicle Interfacelerden türeyen sınıflarımızı tanımladığımızda, organizator sınıfını tanımlarken constructor kısmında bu yeni ihtiyaçlarımızı geçtiğimizde, (Injection) sorunsuz çalışacaktır.
1 2 |
Organizator organizator = new Organizator(new MiniBusDriver(), new MiniBusPassenger()); organizator.MakeTrip(); |
Özet olarak : Yanlış tasarımda organizator kısmında doğru bir genişleme yapmak mümkün değildir. Yeni ihtiyaç geldiğinde resmen proje çöp oluyor yada bin takla atarak kodlarda köklü bir değişiklik yapmak gerekiyor organizator sınıfı için. Doğru olan tasarımda ise , organizator sınıfı kendisine enjekt edilecek sınıfın implementasyonları bildiği için çağıracağı metodun ne olduğunu da biliyor. Organizator sınıfı yeni bir örnek oluşturulurken ,kendisini çağıracak sınıfa çalışabileceği sınıfları tanıtarak bu türe uygun sınıfları kabul edip gevşek bağa uygun olarak enjeksiyon gerçekleştiriyor. Böylece organizator sınıfı Interfaceler ile bir gevşek bağı kurup , alt sınıfları olan bağımlığı bitiriyor. İhtiyacınız ne ise onu sınıf olarak tanımlayıp ilgili interfaceden türetip parametre olarak veriyorsunuz . Dependency Injection, Inversion of Control adını verdiğimiz uygulama akışını değiştirme yönteminin özelleşmiş bir halidir. Olay bu kadar kolay, inşaAllah anlatabilmişimdir umuduyla saygılarımla 🙂
Not: Bu örneğin dinamik olanını IOC Container ile yapabilirdik. Configuration dosyasından alacağımız bir config ayarları ile hangi nesnenin oluşturulacağını dinamik olarak belirleyebiliriz. Bu konudaki örneği IOC Container yazımda yapmış olacağım