Tightly coupled-Loosely coupled Sistemler ve Dependency Injection
1.Dependency Injection
Dependecy injection çoğumuzun kodlarında kullandığı bir pattern olsa da hala tam olarak anlaşılmış olduğunu düşünmüyorum. Dependency injectionu 10 farklı yazılımcıya sorsanız 10 farklı cevap alabilirsiniz 🙂 . Bazı yazılarda dependency injectionun yararının bizi new’lemenin maliyetinden kurtarmak olduğunu da okudum. Sonra dependency injectionla ilgili bişeyler karalamaya karar verdim. Konuyla ilgili ilk söylemek istediğim şey, dependency injection kullanmanın bir amaç değil belli problem ve zorluklara çözüm olduğunun farkında olmak.
Ancak… öncesinde nasıl ki matematikte belli konuları işlemeden diğer konulara atladığınızda, konunun altının boş ve eksik kalacağı gibi bana göre dependency injectiona geçmeden önce de, değinilmesi gereken bazı önemli kavramlar var. Tightly coupled ve Loosely coupled sistemler. İsterseniz dependency injection’a başlamadan önce bu kavramları bir netleştirmeye çalışalım. Eğer yazının sonuna kadar sabırla okursanız en sonunda da dependency injection’un sağladığı faydaları sıralayarak bitirmeyi planlıyorum 🙂 .
2.Tightly Coupled Sistemler
Bir kod parçacığı çalışabilmek için mutlaka içerisindeki spesifik bir nesneye ihtiyaç duyuyorsa bu o nesneye sıkı sıkıya bağlı olduğu anlamına gelir. Ne demek istediğimi bir örnek üzerinden ele alalım. Aşağıda PaymentProcess ve CreditCard adlı classlarımız var. Ancak CreditCard classı PaymentProcess classı içerisinde newlenmiş. Yani PaymentProcess classı CreditCart classı olmadan ele alınamaz. Dolayısıyla CreditCard classındaki bir değişiklik PaymentProcess classını direkt olarak etkiler.
Bahsettiğimiz class PaymentProcess yani Ödeme Sistemleri classı, peki ilerleyen zamanlarda Kredi kartıyla ödeme seçeneğine ilaveten SMS ile ödeme seçeneğinin de eklenmesi gerektiğinde ne yapacağız?. Kodun şuan ki haliyle bu biraz düşündürücü. Yani anlayacağınız PaymentProcess classı CreditCard classına tightly coupled-sıkı sıkıya bağlı.
public class CreditCard
{
public void Pay(decimal amount)
{ //Do something }
public void ShowLimit()
{ //Do something }
}
public class PaymentProcess
{
public PaymentProcess()
{
}
public void Pay(decimal amount)
{
CreditCard creditCard = new CreditCart();
creditCard.Pay(amount);
}
public void ShowLimit()
{
CreditCard creditCard = new CreditCart();
creditCard.Pay(amount);
}
}
Aynı senaryo üzerinden devam edelim. Ve sıkı bağların (Tightly coupled) bize yarattığı sıkıntıları anlayıp aşama aşama bu bağları kırmaya çalışalım. Diyelim ki bu sisteme bir de SMS ile ödeme eklenmesi istendi. Kodun bu haliyle epeyce zor ama biraz düzenleme yapalım ve CreditCardı constructorun içerisinde newleyelim. Eski haline göre biraz daha iyi oldu ama bu hareket ödeme sistemlerinin kredi kartı classına tighly coupled olarak bağlı olduğu gerçeğini değiştirdi mi? Tabi ki hayır.
public class PaymentProcess
{
CreditCard creditCard;
public PaymentProcess()
{
creditCard= new CreditCart();
}
public void Pay(decimal amount)
{
creditCard.Pay(amount);
}
public void ShowLimit()
{
creditCard.Pay(amount);
}
}
E o zaman bir güzellik daha yapalım ve aşağıdaki gibi bu newleme işini PaymentProcess classından çekelim ve bırakalım da bu nesne yaratma sorumluluğu PaymentProcess’in değil de başkasının bir problemi olsun. Kod biraz daha güzelleşti mi? Evet, Peki artık CreditCard classına sıkı sıkıya bağlımıyız? yine EVET 🙂 . Çünkü sistem hala çalışabilmek için ancak ve ancak bir kredi kartı nesnesine ihtiyaç duyuyor.
public PaymentProcess(CreditCard creditCard)
{
this.creditCard= creditCard;
}
Koda bu haliyle Sms ile ödeme sistemi eklemeye kalksak ilk aklımıza gelen yöntem heralde aşağıdaki gibi olurdu. Ancak aşağıdaki yöntem akılcı değil, her yeni ödeme yönteminde ödeme yönetiminin kendi classını oluşturmanın yanı sıra gelip bir de PaymentProcess’e yeni metotlar mı ekleyeceğiz? Kaldı ki open/closed prensibine de uygun değil.
public class PaymentProcess { private CreditCard _creditCard; private SmsPayment _smsPayment; public PaymentProcess(CreditCard credit, SmsPayment smsPayment ) { _creditCard= credit; _smsPayment= smsPayment; } public void PayWithCreditCard(decimal amount) { _creditCard.Pay(amount); } public void ShowCreditCardLimit() { _creditCard.Pay(); } public void PayWithSMS(decimal amount) { _smsPayment.Pay(amount); } public void ShowSMSLimit() { _smsPayment.Pay(); } }
Yine de bu kod belki ihtiyaçları karşılıyor olabilir ama size daha kötü bir haberim var. Artık bir değil iki classa tighly coupled olarak bağlıyız ve her yeni bir ödeme sistemi geldiğinde koda taklalar attırmak zorunda kalacağız. Neyse bu iş daha fazla dallanıp budaklanmadan ve sarmaşık halini almadan Loosely coupled kavramına geçelim.
3.Loosely Coupled Sistemler
Yukarıdaki konsepte baktığımız zaman, müşteri ihtiyaçlarının sürekli genişlediğini dolayısıyla kodun da sürekli genişleme ihtiyacı olduğunu görüyoruz ki bununla günlük hayatımızda sıkça karşılaşmışızdır. Ödeme yöntemi bugün kredi kartıdır, yarın SMS eklenir öbürgün Paypal. Verileri bugün Sql’e yazmamız istenir, yarın mongoDbye… Dolayısıyla hiç bir şeye sıkı sıkıya bağlanmamamız gerekir 🙂
Peki gevşek bağlı bir sistemi nasıl kurabiliriz?
Bağımlılıklarınızı soyut üst kavramlar üzerinden verin!
Ne demek istediğimi yazılım dışı ufak bir örnekle anlatmaya çalışayım.Mesela sizin bir araca ihtiyacınız vardır. Aracın da Hareket etmek. Durmak. Parketmek, YolcuAlmak vs gibi fonksiyonellikleri vardır. O an elinizde taksi vardır. Siz tüm sisteminizi bunun üzerine kurarsınız ama yarın midibus araç sisteme dahil olur diğer gün otobus vs. Ama sizin her zaman ihtiyacınız olan ve sisteme eklenecek olan varlık en nihayetinde araçtır ve çoğu araç ortak özelliklere sahiptir. Araca ihtiyacımız var ama hangi araç? Biraz soyut kaldı sanki. Orası şuan için önemli değil bırakalım soyut kalsın sonuçta soyut kavramların yazılımdaki karşılıkları interfaceler ve abstract classlar ki zaten bunların da nesneleri üretilemiyor e o zaman hangi araç olduğundan bize ne 🙂 Bırakalım da ona runtime’da Latebinding karar versin 🙂 .
Aynı şeyi yukarıdaki ödeme sistemlerine uyarlayalım. Bizim bağımlılığımız direkt olarak herhangi bir ödeme yöntemine (kredi kartı, sms, paypal) olmamalı. Bizim ihtiyacımız olan bir ödeme yöntemi ve herhangi ödeme yöntemi sisteme direkt entegre olabilmeli. Peki şuan hangi ödeme yönteminden bahsediyoruz? Şuan için derdimiz o değil 🙂
Gevşek bağlı sistemler kurmanın en önemli aktörleri interfacelerdir. Çünkü interfaceler classların arayüzleri, imzalarıdır.
Aşağıda IPaymentMethod adında bir interfacemiz içerisinde de Pay ve ShowLimit isimli iki tane metodumuz var. Bu interfacesi kalıtan tüm classlar bu iki metodu implemente etmek zorunda.
interface IPaymentMethod
{
void Pay();
void ShowLimit();
}
CreditCard sınıfımızı IPaymentMethod interfacesinden kalıtıp ilgili metotlarımızı doldurduk.
public class CreditCard : IPaymentMethod
{
public void Pay(decimal amount)
{ //DoSomething }
public void ShowLimit()
{ //DoSomething }
}
SmsPayment classımızı da aynı şekilde IPaymentMethod classından kalıtıp metotları SMSMethod classının kendi ihtiyaçlarına göre implemente ettik.
public class SmsPayment : IPaymentMethod
{
public void Pay(decimal amount)
{//DoSomething }
public void ShowLimit()
{ //DoSomething }
}
Daha sonra PaymentProcess classımızın içersine sıkı sıkıya bağımlı olduğu classları değil de bunların interfacesi olan IPaymentMethod interface’ini verdik ve tüm metotları bu interface üzerinden çağırdık.
Peki bu bize ne gibi bir güzellik sağladı? Daha öncesinde PaymentProcess classı CreditCard classına sıkı sıkıya bağlıydı ve PaymentProcess classını CreditCard nesnesi olmadan ele alamazdık. Hele ki yeni bir payment modeli eklenmek istendiyse al başına belayı 🙂 . Şuan ki durumda yeni bir ödeme modeli istendiğinde tek yapmamız gereken bu ödeme modelini IPaymentMethod interfacesinden kalıtmak ve ilgili metotları doldurmak. Daha sonrasında o zaten otomatik olarak PaymentProcess’e entegre olabilecek çünkü şuan PaymentProcesste IPaymentMethod interfacesiden türeyen her class üzerinden çalıştırılabilir durumda. Yani hiç bir class’a sıkı sıkıya bağlı değil.
public class PaymentProcess{
private IPaymentMethod _paymentMethod;
public PaymentProcess(IPaymentMethod paymentMethod)
{
_paymentMethod= paymentMethod;
}
public void Pay(decimal amount)
{
_paymentMethod.Pay(amount);
}
public void ShowLimit()
{
_paymentMethod.ShowLimit(amount);
}
}
4. Ve Tekrar Dependency Injection
Tightly coupled sistemlerden bahsettik hemen arkasından Loosely coupled sistemlerden bahsettik. Gelelim şimdi asıl konumuza, dependency injection. Dependency injection nedir? sorusuna ufaktan bir giriş yapalım. Dependency injection adından da anlaşılacağı üzere bağımlılıkları enjekte etme yöntemi sunan pattern’dir. Dependency injection ile bağımlı olduğunuz classları ana classınızda göbekten yaratmak yerine onları enjekte edersiniz. Nereden enjekte edersiniz ? Büyük çoğunlukla constructor üzerinden enjekte edersiniz.
Sadece constructor üzerinden mi? Hayır,
- constructor injection
- propery injecion
- method injection
- ambient context
- service locator
gibi bir sürü bağımlılıkları injection yöntemi var ki bunların her birini burada anlatmak yersiz olur o yüzden sadece constructor injection ile property injection’a değineceğim.
4.1 Constructor injection
Constructor injection aslında yukarıda da yaptığımız gibi bağımlılıkları nesne initialize edilirken constructor üzerinden vermemizdir.
private IPaymentMethod _paymentMethod; public PaymentProcess(IPaymentMethod paymentMethod) { _paymentMethod=paymentMethod }
4.2 Property injection
Property injection da bağımlılığın public bir property üzerinden o classı kullanacak kişi tarafından inject edilmesi esasına dayanır. Görüldüğü gibi yine herhangi bir ödeme yöntemine sıkı sıkıya bağlı değiliz. Eğer ki obje yaratma işlemi sizin kontrolünüz dışındaysa kullanılacak injection yöntemi bu olabilir.
public class PaymentProcess { public IPaymentMethod paymentMethod { get; set; } public PaymentProcess() { } public void Pay(decimal amount) { paymentMethod.Pay(amount); } } static void Main(string[] args) { var payment = new PaymentProcess(); payment.paymentMethod = new CreditCard(); //inject işlemi propertynin setterı üzerinden burada yapılıyor. payment.Pay(); }
Gelelim işin aslında, neden dependency injection kullanırız? İlk paragrafta da söylediğim gibi dependency injection kullanmak bir amaç değil bazı sorunların/zorlukların çözümüdür.
– Dependency injection loosely coupled componentler/sistemler tasarlamamızı sağlar. Yani componentlerin birbirlerine sıkı sıkıya değil de gevşek bağlanmış bir yapıda kurulmasını sağlar. Ayrıca dependency injection bize testable kodlar yazma imkanı verir. Aslında dependency injection bize loosely coupled sistem oluşturma olanağı sunar. Loosely coupled sistemler de testeabledır şeklinde bunları birbirine zincir şeklinde bağlayabiliriz.
– Dependency injection ile testable uygulama ve unit testte bize sağladığı kolaylıklar kısmına yazı git gide uzadığı için pek girmedim ama kısaca değinmek gerekirse, unit testler test edilen fonksiyonların tüm dış bağımlılıklardan (servis, database ya da içerisinde çağrılan farklı fonksiyonlar) izole edilerek edilmesidir. İzole etmekten kastımız da bu bağımlılıklar yerine bu bağımlılıkların fakelerini yani mocklanmış halleri üzerinden hareket etmeniz demektir. Yani burdan çıkaracağımız en basit sonuç, yine bu bağımlılıklar sıkı sıkıya olmamalıdır biz yeri geldiğinde bu bağımlılıklar yerine bunların mocklarını da verebiliyor olmalıyız’dır.
– Genişletilmesi kolay kodlar oluşturabiliriz. Aslında yukarıdaki ödeme sistemleri örneğinde de gördüğümüz gibi, sisteme yeni ihtiyaçlar dahil olduğunda yani sistemin genişlemesi gerektiğinde varolan sistemi yıkıp dökmeden, taklalar atmadan kolay bir şekilde genişletebiliriz.
– Bize late bindingin avantajlarından yararlanma imkanı sunar.
İngilizce bilsem de türkçe olarak anlatımını okumak gibisi yok. Teşekkürler 🙂
Çok iyi anlatım olmuş, emeğinize sağlık.