Dependency Injection & Swinject

Mobiler.dev platformunda yazı yazarken başka sitelerden bahsetmek olur mu? “Neden olmasın?” diyerek, ufak bir reklamla konuya giriyorum: https://www.raywenderlich.com/ Geçenlerde raywenderlich’te yayınlanan Advanced iOS App Architecture kitabını karıştırma imkanım oldu. Dependency Injection konusunu o kadar güzel anlatmışlar ki, orada öğrendiğim bakış açılarını burada da paylaşmak istedim.


Bu arada beklentinizi de çok yükseltmek istemiyorum. Temel konulardan bahsedeceğim aslında. Ama bu konuları gayet derli toplu ve anlaşılır bir şekilde anlatmaya çalışacağım.


Görsel Referansı


Dependecy Injection Nedir?


Hızlıca örnekler üzerinden konuya girelim.


Diyelim ki, projemizi geliştirirken yeni bir class yazmaya başladık ve geliştirmemizin herhangi bir anında bu class’ın içerisinde başka bir class’ı da kullanmamız gerekti. Örnek olarak:

Şimdi yukarıdaki class'ta 7. 8. ve 11. satırlara odaklanalım. Dikkat ederseniz 11. satırda getAllWorkerData() metodunu kullandık. (ApiService class'ından türetilmiş bir nesne sayesinde). ApiService class'ından bir nesne türetmek için de ApiService class'ının yapılandırıcı metodunu kullanmamız gerekti (8. satır). Bu yapılandırıcı metodunu kullanabilmek için de Token class'ından türetilmiş bir nesneye ihtiyaç duyduk. Bu nesneyi de 7. satırda türettik.


Yani özetle, UnderConstruction class'ında yapmak istediğimiz işlemleri yapabilmek için ApiService ve Token class'larından nesne türetmek zorunda kaldık. (7. ve 8. satır). Yazmış olduğumuz bu class, 2 farklı class'a bağımlı hale geldi.


Eğer dependency injection yaklaşımını kullansaydık, ApiService class'ından türetilmiş bir nesne, bu yukarıdaki class'ın içerisine hazır bir şekilde inject edilmiş olacaktı. Ve biz de 7. ve 8. satırdaki yapılandırıcı metodlarıyla uğraşmak zorunda kalmayacaktık.


"Sen de amma abarttın ya. Yukarıdaki class'ımız başka class'lara bağımlı olsa ne olacak ki? 2 satır yapılandırıcı metod kodu yazarız, sorunu çözeriz" diyebilirsiniz. Evet bu söylediğiniz de bir çözüm. Ama büyük projelerde böyle bir çözüme giderseniz ileride çok daha büyük problemler yaşayabilirsiniz.


Mesela bu problemler neler olabilir:


1. Maintainability (Sürdürülebilirlik):


Diyelim ki projenizin bir aşamasında ApiService class'ının yapılandırıcı metoduna yeni bir parametre eklemek zorunda kaldınız. Böyle bir durumda ApiService class'ının yapılandırıcı metodunu kullandığınız her yerde tek tek değişiklik yapmak zorunda kalacaksınız. Yapması zor, zaman alan, zahmetli bir iş.


2. Test Edilebilirlik (Testability):


Diyelim ki UnderConstruction classının getWorkerCount() metodu için bir unit test yazdınız ve unit test'iniz fail oldu. Fail olmasının sebebi bağımlı olduğunuz classları türetirken meydana gelen bir hata mıydı yoksa getWorkerCount() metodunun yapması gereken asıl işleri yanlış yapması mıydı? Bunu anlamanız zorlaşacaktır.


3. İkame Edilebilirlik (Substitutability):


Diyelim ki yine getWorkerCount() metodu için unit test yazdınız. Ve ApiService class'ının kullandığı kaynakları, unit test işlemleri yapılırken meşgul etmek istemiyorsunuz. Eğer ApiService class'ını hazır bir şekilde inject etmiş olsaydınız, onun yaptığı işleri fake datalarla yapan başka bir class'ı kolayca inject edebilir ve unit testlerinizi ekstra kaynak kullanmadan yapabilirdiniz.


4- Ertelenebilirlik (Deferability):


Mesela projemizde kullandığımız database teknolojisini değiştirmek istedik. Oluşturduğumuz Database class'ını, Database işlerini yapması gereken class’lara hazır bir şekilde inject ettiysek, Database teknolojisi değişikliğini çok daha kolay yapabiliriz. "A Database" class’ı yerine "B Database" class’ını inject etmemiz yeterli olacaktır. (Database class’larının interface’inin aynı olduğunu kabul ediyoruz.)


5- Geliştirme sırasında kontrol (Control during development):


Diyelim ki Projenizin bir login ekranı var. Ve bu ekranda çalışan LoginRequest class'ının login() metodundan true yanıtı almadan bu ekranı geçemiyorsunuz. Yani girişte her seferinde kullanıcı adı şifre bilgisi girip login() metodunun işlemlerini tamamlaması için vakit harcıyorsunuz. Eğer LoginRequest class'ını hazır bir şekilde inject etmiş olsaydınız, bu class'ı aynı işleri fake data'larla yapan başka bir class'la kolayca yer değiştirebilirdiniz. Böylelikle login işlemi hızlıca otomatik olarak gerçekleşirdi ve zamandan tasarruf etmiş olurdunuz.


6- Nesne yaşam sürelerini en aza indirme (Minimizing object lifetimes):


Herhangi bir projede, bir developer tek seferde ne kazar az konuya odaklanıyorsa hata yapma ihtimali o kadar azalacak ve kodun anlaşılabilirliği de artacaktır. Yani yukarıdaki kod örneğimize dönersek, developer yukarıdaki kodu yazarken ApiService class'ından nasıl nesne türetileceğine değil, sadece onu kullanıp gerekli işlemleri yapmaya odaklanmalıdır.


7- Tekrar kullanılabilirlik (Reusability):


Classlarımızı bu tarz bağımlılıklardan kurtardığımız takdirde, bu bağımsız class'ları başka projelerimizde de (Gerekli inject işlemlerini yaparak) rahatça kullanabiliriz.


Peki inject işlemlerini nasıl gerçekleştiriyoruz. Aslında kod yazan arkadaşlar bu işlemleri zaten projelerinde kullanıyorlardır muhtemelen. Sadece adına "inject" demiyor olabilirler. Şimdi biz biraz daha detaylı inceleyelim.


Inject işlemini 3 farklı şekilde yapabiliriz:


1- Yapılandırıcı Metod (Initializer):


Yine yukarıdaki kod örneğinden devam edelim. Yukarıdaki class'ta ApiService ve Token class'ından türetilen nesnelere ihtiyaç duymuştuk. Bu nesneleri getWorkerCount() metodunun içerisinde türetmiştik. Bunun yerine, aşağıdaki gibi yapabiliriz:


Yukarıda gördüğümüz gibi, artık getWorkerCount() metodunun içerisinde başka class'ları türetmekle uğraşmadık. İhtiyaç duyduğumuz nesne, üzerinde çalıştığımız class'ın yapılandırıcı metoduna parametre olarak hazır geldi. Ve biz de bunu class’ımızın içinde rahatça kullanabildik. Artık UnderConstruction class'ının kullanımı şöyle olacaktır:



2- Parametre (Property):


Aynı class üzerinden konuşmaya devam edelim. Ancak bu sefer class'ımızı aşağıdaki gibi tasarlayalım:


Dikkat ederseniz bu sefer UnderConstruction metodunun yapılandırıcı metodu bulunmuyor. Yani ApiService tipindeki bir nesne, yapılandırıcı metod aracılığıyla elimize ulaşmadı. Peki bu nesne nereden gelecek? Aslında bunun için UnderConstruction class'ının kullanılacağı yeri incelememiz gerekiyor. Kullanılacağı yer şöyle olmalı:


UnderConstruction class'ından bir nesne türettikten sonra, bu nesnenin içerisindeki "service" parametresine ApiService class'ından türettiğimiz nesneyi inject ediyoruz. Böylelikle getWorkerCount() metodu doğru çalışabiliyor.


3- Metod (Method):


Bu yöntemi anlatabilmek için class'ımızı şu hale getirelim:


Buradaki kod örneğinde ise, ihtiyaç duyduğumuz ApiService nesnesi getWorkerCount() metoduna parametre olarak gönderiliyor. Bu metodun içerisinde bu parametreyi rahatça kullanabiliyoruz. Kullanım örneği de şöyle olacak:


Dependency Injection Yaklaşımları


Şimdi biraz daha derine dalalım ve Dependency Injection yaklaşımlarını inceleyelim. Bu incelemeleri yaparken yine UnderConstruction class'ı üzerinden örnekler vermeye devam edeceğim. (UnderConstruction class'ının ApiService class'ına bağımlı olduğunu unutmayalım.)


1- On-demand:


Bu yaklaşımda UnderConstruction class'ını kullanmak istediğimiz herhangi bir yerde, ApiService class'ından bir nesne türetip, bu nesneyi bir şekilde (yukarıdaki 3 yöntemden biri ile) UnderConstruction class'ına inject ediyoruz. Dikkat ederseniz, UnderConstruction class'ını kullanmaya ihtiyaç duymadan önce, herhangi bir ApiService nesnesi türetilmiyor. Ne zamanki UnderConstroctur'ı kullanmaya ihtiyaç duyuyoruz, ancak o zaman ApiService nesnesi türetip, bu türettiğimiz nesneyi UnderConstruction class'ına iletiyoruz. Bu yaklaşıma On-Demand yaklaşımı denmektedir. Yapılandırıcı Metod yöntemini kullanarak bir örnek verelim (Yukarıdaki "Yapılandırıcı Metod" örneği ile aynı örnek aslında):



2- Factories:


Yine UnderConstruction class'ını kullanmak istediğimizi varsayalım. Kullanmaya başlamadan önce ApiService nesnesini UnderConstruction'a inject etmemiz gerekiyor. Dolayısıyla ApiService class'ından bir nesne türetmemiz gerekiyor. İşte bu aşamada ApiService class'ından nesne türetme işini bir Factory class'ının içinde yapıyoruz. Dolayısıyla UnderConstruction metodunu kullanacağımız zaman ApiService nesnesi türetmekle uğraşmaktansa, ApiService nesnesinin hazır halini bir Factory class'ından alıyoruz. Kodla örnekleyelim:


Kullanımı da şöyle olacak:


Dikkat ederseniz Factory class’ı, ApiService class’ından bir nesne türetmek için yapılması gereken tüm işleri kendi içerisinde yaptı (Token servisinden bir nesne türetmek, bu nesneyi kullanarak ApiService class’ının yapılandırıcı metodunu çalıştırmak gibi…) Böylelikle UnderConstruction class’ını, diğer nesnelerin türetilme işleriyle uğraşmadan kullanabildik. Diğer nesneler Factory class’ının içerisinden hazır bir şekilde aldık.



3- Single-Container:


Factories yaklaşımını inceleyecek olursak şunu görüyoruz: Factory class'ının getApiService() metodu her çağırıldığında ApiService nesnesi tekrar oluşturuluyor. Bu nesneyi her seferinde tekrar oluşturmak yerine bir kere oluşturup, her ihtiyaç duyulduğunda aynı nesneyi tekrar kullanmak mümkün. Onun için de şöyle bir Container class'ı yazabiliriz:



Kullanımı da şöyle olacak:


Dikkat ederseniz bu yaklaşımda ApiService nesnesi sadece bir kere türetiliyor ve aynı nesne, ihtiyaç duyulan her yere inject edilebiliyor. (Factories ve Single-Container yöntemleriyle ilgili çok daha farklı kullanımlar ve avantajlı-dezavantajlı durumlar var. Bunları yazının sonunda tekrar bahsedeceğim kitapta bulmanız mümkün.)


Dependency Injection'ın nasıl yapıldığını ve uygulanabilecek yöntemleri anlatmış olduk. Gördüğünüz gibi tüm bu yaklaşımları özel bir dependency injection frameworkü kullanmadan yaptık. Ama son olarak bir dependency injection framework'ünden bahsetmek istiyorum: Swinject.


Swinject


Bu konuyu da örnek özerinden anlatmaya devam edelim. Örneğimizdeki projede işlem yapılan 2 tane ekran var. Birincisi kullanıcı adı-soyadı-mail adresi gibi bilgileri UserDefaults’a kaydettiğimiz bir ekran. Diğeri de kullanıcının tercih ettiği bildirim ayarlarını yine UserDefaults’a kaydettiğimiz başka bir ekran. UserDefaults işlemlerini yapabilmek için hazırladığımız class şu şekilde:



Controller class'larımızdan bir tanesi de şu şekilde:


Controller class’ını incelediğimizde şunu görüyoruz: UserDefaultsService tipinde bir nesne kullanılmış. Ama bu nesnenin türetilme işlemi hiçbir yerde yapılmamış. Yani bu nesne bu class’a hazır bir şekilde inject ediliyor olması lazım. Peki bu inject işlemi nerede ve nasıl gerçekleşiyor. Bunu anlamak için hemen aşağıdaki class’ı inceleyelim:


3. ve 5-6. satırlar bizim için önemli satırlar.


3. satır: UserDefaultsService class'ından bir nesne oluşturuluyor ve container'a koyuluyor.

5-6. satır: UserInfoViewController class'ı oluşturulduğu anda, container'ın içerisindeki UserDefaultsService nesnesi UserInfoViewController class'ındaki userDefaultService parametresine setleniyor. Böylelikle inject işlemi Container yöntemiyle gerçekleşmiş oluyor.


Şimdilik bu konuyla ilgili anlatabileceklerim bu kadar. hem özel bir framework kullanarak hem de kullanmayarak Dependecy Injection konusunu detaylı bir şekilde ele almış olduk. İlgili arkadaşlara bu kitabı okumalarını mutlaka öneririm:


https://www.raywenderlich.com/14062732-advanced-ios-app-architecture-now-fully-updated


Daha kısa makaleler için de buralara göz atabilirler:

https://www.raywenderlich.com/17-swinject-tutorial-for-ios-getting-started

https://www.raywenderlich.com/14223279-dependency-injection-tutorial-for-ios-getting-started



Sonuç

  • Dependency Injection yaklaşımını uygulamak özellikle büyük projelerde bize çok büyük avantajlar sağlıyor.

  • Dependency Injection yöntemlerini uygulayabilmek için özel bir framework kullanmak zorunda değiliz. Dependency Injection mantığını iyi bilirsek bu yapıyı kendimiz de kurgulayabiliriz.

  • Swinject gibi framework'ler Dependency Injection konusunda işimizi çok kolaylaştırabilir.

Swinject ile ilgili örnek bir projeyi de bu linkten indirebilirsiniz: https://github.com/Cevat/SwinjectExample


Konuyla ilgili soru sormak ya da konuşmak isterseniz bana mail adresim üzerinden ulaşabilirsiniz: cevatbalaban@gmail.com


Hepinize sevgiler:)


#DependencyInjection #Swinject #ios

0 yorum

Son Paylaşımlar

Hepsini Gör

Komünite

Platform

Mobiler.dev Anasayfa
  • Twitter
  • Instagram
  • development_düzenlendi_düzenlendi
  • Youtube
  • slack-icon-black_edited_edited_edited
  • Gri LinkedIn Simge
imageedit_2_9667998092.png
JetBrains Hakkında Detaylı Bilgi Alın

© 2021 by mobiler.dev

Kurumsal Yazar Hesapları

adesso.png
mobilerdevLogo.jpg
Yazarlık Başvurusu Hakkında Bilgi Alın, Başvuru Yapın.
Topluluk Yazarlarını Tanıyın