Dependency (Inversion | Injection)

Karantinanın onuncu gününden herkese merhaba 👻

Inversion of Control (IoC), Dependency Inversion Principle(DIP), Dependency Injection (DI) kavramlarını anlamak oldukça karmaşık olabiliyor (Benim için böyle oldu 😩). Bu yazımda bu kavramları elimden geldiğince anlatmaya çalışacağım.

Aslına bakacak olursak bu üç kavram da hemen hemen benzer durumları ele alır. IoC ve DIP, uygulama sınıfları tasarlanırken kullanılması gereken high level design prensipleridir. Bunlar prensip olarak adlandırıldığı için ne (best practice) yapılması gerektiğini önerirler fakat nasıl (implementation) yapılacağını belirtmezler. DI bir design pattern’dır ya da DIP’nin bir implementasyonudur. Aşağıdaki şekilde anlatılanlar daha net anlaşılabilir:

Principles and Patterns (tutorialsteacher)

Inversion of Control : Uygulama içerisinde kullanılan obje instance’larının yönetimi sağlar ve bağımlılığı en aza indirgemeyi (loose coupling) amaçlar. Projedeki bağımlılıkların oluşturulmasını ve yönetilmesini geliştiricinin yerine, framework’ün yapması olarak da açıklanabilir.

Somut bir örnek verecek olursak: İşe giderken kendi arabanızı kullandığınızı varsayalım. Bu, arabayı kontrol ettiğiniz anlamına gelir. IoC prensibi kontrolü tersine çevirmeyi önerir, yani arabayı kendiniz sürmek yerine başka bir kişinin arabayı süreceği bir taksi kiralarsınız. Böylece, buna kontrolün tersine çevrilmesi denir (siz — control → taksi şoförü). Siz araba kullanmak zorunda kalmadığınız için ana işinize odaklanabilir, taksi şoförünün ise arabayı kullanmasına izin verebilirsiniz. Özetle IoC prensibi, test edilebilir, sürdürülebilir (maintainable) ve genişletilebilir (extensible), loose couple sınıfların tasarlanmasına yardımcı olur.

Kod örneğinin daha anlaşılabilir olması için bazı konuları hatırlatmak istiyorum. Object-oriented bir tasarımda, sınıflar arasındaki bağımlılık loose coupled bir şekilde tasarlanmalıdır. Bu bir sınıfta yapılan değişikliklerin ona bağlı olan sınıfları değişmeye zorlamaması anlamına gelir, böylece tüm uygulama sürdürülebilir ve genişletilebilir hale gelebilir.

Typical n-tier Architecture (tutorialsteacher)

Klasik n-katmanlı mimaride, kullanıcı arayüzü (UI) veri almak veya kaydetmek için Service katmanını kullanır. Service katmanı, verilere iş kurallarını uygulamak için BusinessLogic sınıflarını kullanır. BusinessLogic sınıfı, verilerin veritabanından alındığı veya kaydedildiği DataAccess sınıfına bağlıdır. IoC’yi anlamak için BusinessLogic ve DataAccess sınıflarına odaklanalım:

Yukarıdaki örnekte görüldüğü gibi, CustomerBusinessLogic ve DataAccess sınıfları birbirine sıkı sıkıya bağlıdır, çünkü CustomerBusinessLogic sınıfı müşteri verilerini almak için DataAccess sınıfının bir nesnesini oluşturur ve bu nesnenin life-cycle’ını yönetir.

  • DataAccess sınıfındaki değişiklikler CustomerBusinessLogic sınıfında değişikliklere yol açacaktır. (metodların eklenmesi, kaldırılması, isminin değiştirilmesi vb.)
  • Projeye yeni feature’ların eklenmesi (müşteri verilerinin farklı veritabanlarından, web servislerden gelmesi durumu) gelecekte farklı sınıflar oluşturmamız gerektireceği için bu CustomerBusinessLogic sınıfında değişikliklere yol açacaktır.
  • CustomerBusinessLogic sınıfı, new keyword’unu kullanarak DataAccess sınıfının bir nesnesini oluşturur. DataAccess sınıfını kullanan ve nesnelerini oluşturan birden çok sınıf olabilir. Bu, aynı sınıftan nesneler oluşturmak ve bağımlılıklarını korumak için tekrarlayan bir koddur.
  • CustomerBusinessLogic sınıfı somut DataAccess sınıfının bir nesnesini oluşturduğundan, bağımsız olarak test edilemez (TDD). DataAccess sınıfı bir mock sınıfla değiştirilemez.

IoC prensibini uygulamak için birçok pattern mevcut:

IoC Patterns (tutorialsteacher)

Factory desing pattern’ı kullanarak yukarıdaki kodu tekrar düzenleyelim:

DataAccess sınıfından nesneler türetmek isteyen sınıflar için DataAccessFactory sınıfını oluşturduk. CustomerBusinessLogic sınıfı DataAccess sınıfının bir nesnesini new keyword’unu kullanarak oluşturmak yerine DataAccessFactory.GetCustomerDataAccessObj() metodunu kullanır.

Sadece IoC kullanarak tamamen loose coupled sınıflara ulaşamayız. IoC ile birlikte DIP ve DI da kullanmamız gerekir.

Dependency Inversion Principle : Robert Martin (Uncle Bob) tarafından bulunan SOLID prensiplerinden biridir. DIP der ki:

  • High-level modüller, low-level modüllere bağımlı olmamalı, her ikisi de abstraction’lara bağımlı olmalıdır.
  • Abstraction’lar da detaylara bağımlı olmamalı, detaylar abstraction’lara bağımlı olmalıdır.

Bu beyin yakan ifadelerden sonra IoC’de bağımlılıklarından yavaş yavaş kurtarmaya başladığımız örnek üzerinden devam edelim. CustomerBusinessLogic sınıfında nesne oluşturma işlemini Factory sınıfına invert etmiş olsak da yine de somut DataAccess sınıfını kullanmakta ve bağımlılığı devam etmektedir (tightly coupled). Örneğimizde, CustomerBusinessLogic sınıfı, DataAccess sınıfına bağlı olduğu için high-level modüldür. DIP’nin ilk kuralına göre, CustomerBusinessLogic somut DataAccess sınıfına bağlı olmamalı, bunun yerine her iki sınıf da abstraction’a bağlı olmalıdır.

Abstraction, somut olmayan bir şey anlamına gelir. Örneğimizdeki CustomerBusinessLogic ve DataAccess somut sınıflardır, yani bu sınıfların nesnelerini oluşturabiliriz. Programlamadaki abstraction, nesnelerini oluşturamayacağımız bir interface veya abstract class oluşturmak anlamına gelir. DIP’ye göre, CustomerBusinessLogic (high-level modül) somut olan DataAccess sınıfına (low-level modül) bağlı olmamalıdır. Her iki sınıf da abstraction’lara bağlı olmalıdır, yani her iki sınıf da bir interface veya abstract class’a bağlı olmalıdır.

İşe CustomerBusinessLogic’in kullandığı DataAccess sınıfına ait GetCustomerName() metodunu bir interface’e taşımakla başlayalım.

Bu interface’i implement eden CustomerDataAccess sınıfnı oluşturuyoruz.

Şimdi, somut DataAccess sınıfı yerine abstract ICustomerDataAccess döndüren factory sınıfımızı değiştiriyoruz.

Son olarak, CustomerBusinessLogic sınıfnı somut DataAccess sınıfını kullanmak yerine ICustomerDataAccess interface’ini kullanacak şekilde değiştiriyoruz.

Böylece, high-level (CustomerBusinessLogic) ve low-level (CustomerDataAccess) modülleri bir abstraction’a (ICustomerDataAccess) bağlayarak DIP’yi uygulamaya çalıştık. Ayrıca, abstraction (ICustomerDataAccess) ayrıntılara (CustomerDataAccess) değil, ayrıntılar abstraction’a bağlı olmuş oldu. Artık, ICustomerDataAccess’i farklı bir uygulama ile uygulayan başka bir sınıfı kolayca kullanabiliriz.

Yine de, CustomerBusinessLogic sınıfı ICustomerDataAccess interface’inin referansını almak için bir factory sınıfı içerdiğinden, tamamen loose coupled sınıflara ulaşamadık. Bu noktada bize Dependency Injection, Strategy Pattern gibi design pattern’lar yardımcı olabilir. Ancak ServiceLocator gibi anti-pattern’lardan da kaçınmak gereklidir.

Antipattern, iyi bir çözüme benzeyen, ama gelecekte sorunlara yol açan çözümlerdir.

Yazılım geliştiren herkes, problemlerle karşılaşır ve bunları çözmek için birden fazla çözüm yolu bulabilir. Peki bulduğunuz çözüm yolu, iyi bir çözüm müdür? İleride sizin başınıza yeni işler açabilir mi? Yazılımda bir değişiklik gerektiğinde, bunu engelliyor mu? Çözümünüz, yazılımın uzun süre çalışması durumunda bellek sorunlarına yol açıyor mu? Bu soruları istediğimiz kadar arttırabiliriz. AntiPattern’lar, çok sık karşılaşılan ortak sorunlardaki kötü çözümleri size sunar. Bu çözümler ile kendi çözümlerinizi karşılaştırırsınız ve çözümünüzün kalitesi hakkında fikir sahibi olursunuz. Size hangi çözümü uygulamamanız gerektiğini gösterirler.

Kötü çözümleri belirlemek, iyi çözümleri belirlemek kadar yararlı olabilir.

Biz bu örnek için Dependency Injection pattern’ını uygulayacağız.

Dependency Injection : IoC’yi uygulamak için kullanılan bir design pattern’dır. Bağımlı olan nesnelerin sınıf dışında oluşturulmasını ve bu nesneleri sınıfa farklı yollarla gönderilmesini sağlar. DI Pattern üç tip sınıf içerir:

  1. Client Class : Service sınıfına bağımlı (dependent) olan sınıf
  2. Service Class : Client sınıfına hizmet sağlayan sınıf (dependency)
  3. Injector Class : Service sınıfıının nesnesini, client sınıfına inject eden sınıf
DI Classes (tutorialsteacher)

Injector sınıfı, service sınıfının bir nesnesini oluşturur ve bu nesneyi bir Client nesnesine inject eder. Bu şekilde DI , Service sınıfından nesne oluşturmayı Client sınıfının sorumluluğundan ayırır.

Dependency Injection’ı gerçekleştirmenin üç farklı yolu vardır.

  1. Constructor Injection : Injector, servis objesini client sınıfının constructor’ında sağlar.
  2. Property / Setter Injection : Injector, client sınıfındaki public property ile bağımlılığı sağlar.
  3. Method Injection : Client sınıfı bağımlılığı sağlamaya yarayan metodları içeren bir interface implement eder ve injector da bu interface’i kullanır.
DI Example (tutorialsteacher)

Örneğimiz için de görüldüğü gibi CustomerService sınıfı, loose coupling’i sağlamak için service sınıfının nesnesini; constructor, property ya da method aracılığıyla Client sınıfına sağlayan bir injection sınıfıdır.

Constructor injection’ı birlikte uyguladıktan sonra kaynaklarda belirttiğim adresten property ve method injection’ı da inceleyebilirsiniz.

CustomerBusinessLogic, ICustomerDataAccess türünde bir parametre alan constructor içerir. Şimdi onu çağıran bir sınıf, ICustomerDataAccess nesnesi inject etmelidir.

CustomerService sınıfı, CustomerDataAccess nesnesini oluşturur ve CustomerBusinessLogic sınıfına inject eder. Böylece, CustomerBusinessLogic sınıfının new keyword’ü veya factory sınıfını kullanarak CustomerDataAccess nesnesini oluşturması gerekmez. Calling class (CustomerService), uygun DataAccess sınıfını oluşturur ve CustomerBusinessLogic sınıfına ayarlar. Bu şekilde, CustomerBusinessLogic ve CustomerDataAccess sınıfları “daha” loose coupled sınıflar haline gelir.

IoC, DIP ve DI kavramlarını basit örneklere dayandırarak anlatmaya çalıştım. Buraya kadar okuduğunuz için teşekkür ederim.

Görüşmek üzere 🎈

Kaynaklar

https://www.tutorialsteacher.com/ioc/

Emre Taş — Anti-Pattern’ler http://www.csharpnedir.com/articles/read/?id=479&title=Anti-Pattern%27ler

https://livebook.manning.com/book/dependency-injection-in-dot-net/about-this-book/ (Daha detaylı bilgi için kaynak olabilir.)