Yazılım dünyasında ürünlerden daha çok prensiplere ve ilkelere önem verenlerdenim. Java veya C# dilinde yazmışım çokta dert değil benim için. Bir dilin yazım kurallarını ve ürün geliştirme araçlarını öğrenmek çokta uzun zaman almaz. Yazılım geliştirdiğimiz ortamların (IDE) pratiklerini anlamak çok önemlidir elbette. Ama yazılım prensipleri ve ilkelerini benimsemek, o ilkelere sadık kalarak yazılım geliştirmek bana göre daha önemlidir. Bu yazımızda da yazılım geliştiricilerin zamanla edindiği tecrübelereden biri olan Dependency Injection tasarım deseni üzerinde duracağız.
Resimde ne görüyorsunuz diye bir soru sorsam, süpheli süpheli bakmaya mı başlarsınız, yoksa USB anahtarlıklar ve iki anahtar mı dersiniz bilmiyorum. Ama sistematik bir görüntü var aslında resimde. Canı isteyen sistemdeki anahtarlıkları halkadan boşandırıp değiştirebilir. Çünkü halka esnek bir yapıda yani birbirine sabitlenmemiş. Aynı şekilde isteyen, ipliğin ucundaki USB aygıtlarını da istediği renkle değiştirebilir. Çünkü iplikte basit bir düğüm, dolayısıyla anahtarlık ve anahtarlar arasında gevşek bir bağ mevcut. Aynı şekilde USB aygıtlarının kapakları da birbirine uyumlu olduklarından istenen renkte anahtarlık veya kapak seçilebilir ve ikili oluşturulabilir. Bu durum sistemin esnek olduğunu ve tüm bileşenler arasında gevşek bir bağlantı söz konusu olduğunu göstermekdir.
Yazılım tasarım desenlerinde de sıklıkla karşılaştığım “gevşek bağlılık” deyiminin de ifade etmek istediği şey yukarıdaki şekilde anlatıldığı gibidir. Nesnelerin birbirine uyumlu ve birbirini etkilemeyecek şeklide olmasıdır.
Yazılım tasarım desenlerinden biri olan, Dependency Injection (Bağımlılıkların Dışarıdan Enjekte Edilmesi) deseni de bileşenlerin(components) birbirine olan sıkı bağlarının gevşetilmesini ve yazılımın test edilebilir bir hale getirilmesini amaçlamaktadır. Test edilebilir tasarım konusuna daha sonraki yazılarda değinmek istiyorum. Şu anda ilgileneceğimiz konu tasarımdaki gevşek bağlılık(Loosely Coupled) üzerinden ilerleyecektir.
Bir örnek uygulama üzerinden devam etmelim. Senaryomuz bir şirketin çalışanlarının aylık ve günlük satış istatistiklerini veren bir sınıfın tasarlanması şeklinde olacaktır.
public interface IEmployeeStatistics { decimal GetDailySales(int employeeId); decimal GetMonthlySales(int employeeId); }
Yukarıdaki arayüz(interface), diğer bir deyişle sözleşme, employeeId bilgisi verilen çalışanın günlük ve aylık satış tutarlarını verecek şekilde hazırlanmıştır.
Aşağıdaki EmployeeStatistics sınıfı ise IEmployeeStatistics arayüzünden türetilmiş sınıfların getireceği istatistikleri dış dünyaya sunmaktan sorumlu olacaktır.
public class EmployeeStatistics { private IEmployeeStatistics employeeStatistics; public EmployeeStatistics(IEmployeeStatistics employeeStatistics) { this.employeeStatistics = employeeStatistics; } public decimal GiveMeDailyReports(int employeeId) { return employeeStatistics.GetDailySales(employeeId); } public decimal GiveMeMonthlyReports(int employeeId) { return employeeStatistics.GetMonthlySales(employeeId); } }
EmployeeStatistics sınıfının kurucu metodundaki bağımlılık, IEmployeeStatistics arayüzü tarafından sağlanmıştır.
Dış dünyaya sunum yapan sınıf olan EmployeeStatistics sınıfı, eline gelen istatistiklerin hangi veritabanından kullandığını, hangi aşamalardan geçtiğini, ne zorluklar çektiğini bilmez. Hatta sınıfın adını bile bilmez. EmployeeStatistics sınıfı için önemli olan sadece constructor metoduna gelecek olan tipin IEmploeyeeStatistics arayüzünü uygulamış (implement) olmasıdır. Aynen yukardaki resimde anahtarlığa bağlanan USB anahtarlıkların ne şekilde olduğunun önemsiz olduğu gibi.
Şimdi de MSSQL veritabanından istatistik çeken sınıfı oluşturalım.
public class EmoleeStatisticsFromMSSQL : IEmployeeStatistics { public decimal GetDailySales(int employeeId) { //..... //..... // MSSQ'den getirme işlemleri decimal result = 5214M; return result; } public decimal GetMonthlySales(int employeeId) { //..... //..... // MSSQ'den getirme işlemleri decimal result = 23514M; return result; } }
Eğer EmployeeStatistics sınıfı içerisinde IEmployeeStatistics tipini değilde şu anda oluşturduğumuz EmployeeStatisticsFromMSSQL tipini direk kullansaydık iki sınıf arasında (EmployeeStatistics ve EmployeeStatisticsFromMSSQL) sıkı bir bağ oluşacaktı. Bu durumda, sistemde çalışma zamanında(runtime) EmployeeStatisticsFromMSSQL sınıfı yerine EmployeeStatisticsFromOracle gibi bir değişimi imkansız hale getirecekti. Ancak bu sıkı bağı IEmployeeStatistics arayüzü ortadan kaldırdı ve esnek bir bağ oluşturdu. Artık çalışma zamanında istenen sınıf devreye alınabilir. İstediğimiz sınıfı nasıl devreye alacağımız ise EmployeeStatistics sınıfına baktığımızda anlaşılabilmektedir. Sınıf içerisine Constructor seviyesinde bir enjeksiyon yapılmaktadır. Bağımlılıklar dışarıdan sınıf içerisine enjekte edilmektedir.
Bir süre sonra şirketiniz veritabanı yazılımını MSSQL yerine Oracle tarafına geçirmek istediğinde yapmamız gereken sadece EmployeeStatisticsFromOracle sınıfını oluşturmak olacaktır.
public class EmoleeStatisticsFromOracle : IEmployeeStatistics { public decimal GetDailySales(int employeeId) { //..... //..... // Oracle'dan getirme işlemleri decimal result = 5214M; return result; } public decimal GetMonthlySales(int employeeId) { //..... //..... // Oracle'dan getirme işlemleri decimal result = 23514M; return result; } }
Bu sayede yazılımımız değiştirilmeye kapalı , geliştirilmeye açık hale gelmiş olacaktır. Yani yazılım prensiplerinden biri olan Open Closed (Gelişime açık değişime kapalı) prensibine de uygun hale gelmiştir.
Sınıfın kullanımı ise şu şekilde olacaktır.
public class Run { public void Raporla() { IEmployeeStatistics reportProvider = getRaporUretici(); EmployeeStatistics statistics = new EmployeeStatistics(reportProvider); var daily = statistics.GiveMeDailyReports(1); var monthly = statistics.GiveMeMonthlyReports(1); } private IEmployeeStatistics getRaporUretici() { //Web.config dosyasından hangi veritabanından çalışılacak, öğren. //"OR" ise Oracle, "MS" ise MSSQL var db = getDBFromConfig(); IEmployeeStatistics reportProvider; if(db == "MS") reportProvider = new EmoleeStatisticsFromMSSQL(); if(db == "OR") reportProvider = new EmoleeStatisticsFromOracle(); else reportProvider = new NullStatistics(); return reportProvider; } }
Artık kodumuz değiştirilmeden geliştirilebilir haldedir. Yani config dosyasından belirlenen bir flag ile çalışan sisteme müdahale edilebilmektedir. Bu imkanı kolaylaştıran yöntem ise bağımlılıkların dışarıdan alınmasını sağlayan, Dependency Injection tasarım desenidir.
Kullanıma hazır bir yapıyı oluşturabildik. Tekrar görüşmek ümidiyle.
faydalı bilgi elinize sağlık;
eksiklikler;
(db =”MS”) (db =”OR”) // == çift eşittir olacak.
IEmployeeStatistics reportProvider; //if koşulu sağlanmaz ise patlayacağı için default bir değer atamak gerekiyor.
saygılar.
Teşekkürler @muhammed , kodları site üzerinden yazarken böyle eksiklikler çıkabiliyor. IDE üzerinden aldığım kodlar olmuyor bazen direk yönetim panelinden yazıyorum. reportProvider konusunda da Null Object pattern kullanılabilir. Yeri gelmişken onu da eklemiş oldum. İlgin için teşekkürler tekrar.