Not: Bu yazı 2018 yılında medium’da yayınladığım aynı başlıklı yazıdan kopyalanmıştır.
Dependency Injection, yani Türkçede Bağımlılık Enjeksiyonu olarak ifade edebileceğimiz kavram, kodumuzu yazarken kullanmaya ihtiyaç duyulan (bağımlı olduğumuz) diğer kodları, ihtiyaç duyan kodun içerisinde kullanmak (enjekte etmek) olarak tanımlanabilir.
Örneklerle tekrar ifade edecek olursak; elimizde, array üzerinde binary search uygulayarak istediğimiz elemanın indexini bulmayı sağlayan basit bir servis olduğunu düşünelim. Bir Array üzerinde binary search uygulayabilmemiz için gerekli olan koşul, Array elemanlarının sıralı şekilde tutuluyor olmasıdır. Buradan hareketle, binary search servisimizi, array elemanlarını sıralama işlemi gerçekleştiren başka bir servise veya sınıfa bağımlı şekilde oluşturmamız gerektiğini söyleyebiliriz.
public class BinarySearchService {
private SortAlgorithm sortAlgorithm;
public BinarySearchService() {
sortAlgorithm = new BubbleSortAlgorithm();
}
public int search(int[] arrayToSearch, int target) {
// Bagimlilik olarak tanimlanan sortAlgorithm kullanilarak
// array'in siralanmasi
int[] sortedArray = sortAlgorithm.sort(arrayToSearch);
// BinarySearch implementasyonu ve aranan elemanın
// indeksinin bulunmasi
...
return index;
}
}
public class Application() {
public static void main(String[] args) {
...
BinarySearchService service = new BinarySearchService();
int targetIndex = service.search(arrayToSort, 5);
...
}
}
Servisimizin constructor’ında, bağımlı olduğumız sınıftan nesne yarattık ve sıralama yani sort işlemini bu nesne üzerinden yürüttük. Başka bir deyişle, sıralama işlemini gerçekleştiren BubbleSortAlgorithm sınıfımızı, BinarySearch servisine bağımlılık olarak tanımlamış olduk.
Dependency Injection alternatifi olan daha ilkel yöntem ise şuydu: Sort işlemini BinarySearchService’in içerisinde yapmak. Peki ama ileride sort işlemini Bubble Sort değil de başka bir algoritma; örneğin Quick Sort kullanarak yapmak isteseydik ?
Bu senaryoda BinarySearchService’in içerisindeki kodu tekrar düzenlememiz gerekecekti. Peki bu ne gibi bir sorun yaratıyor ?
Aslında Dependency Injection kullanmadığımızda ortaya çıkacak sorunlardan bahsederken, Dependency Injection’ın sağladığı faydaları da görmüş olduk. Faydaları bu ikisiyle sınırlı olmasa da, Seperation of Concerns (Farklı işleri yapan birimlerin birbirinden ayrılması) prensibine uygun ve dinamik bir yapı kurmamızı sağladı.
Dinamik yapı demişken, şimdi yazdığımız koda geri dönelim. Bir sorun göze çarpıyor. Her ne kadar basit haliyle Dependency Injection uygulamış olsak da, dinamik bir yapı kurmuş gibi görünmüyoruz. Çünkü yazdığımız servis Constructor’ında BubbleSort sınıfından nesne yaratılıyor ve her halükarda bu sınıf kullanılıyor. Yani Servis sınıfımız ve BubbleSort sınıfımız tightly coupled (sıkı sıkıya bağlı). Sistemi daha dinamik hale getirmek için bu sınıflar loosely coupled (gevşekçe bağlı) olmalı.
public class QuickSortAlgorithm extends SortAlgorithm {
@Override
public int[] sort(int[] arrayToSort) {
// QuickSort
...
}
}
public class BinarySearchService {
private SortAlgorithm sortAlgorithm;
public BinarySearchService(SortAlgorithm sortAlgorithm) {
this.sortAlgorithm = sortAlgorithm;
}
public int binarySearch(int[] arrayToSearch, int target) {
sortAlgorithm.sort(arrayToSearch);
}
}
public class Application() {
public static void main(String[] args) {
.....
// Üzerinden servisi kullanacağımız nesneyi yaratırken
// parametre olarak istediğimiz sort sınıfını verdik.
BinarySearchService binarySearchService = new
BinarySearchService(new QuickSortAlgorithm());
int targetIndex = binarySearchService.sort(arrayTosort, 5);
.....
}
}
Bu örnekte polymorphismden yararlanarak servis ve diğer kullandığımız sınıfları loosely coupled olacak şekilde düzenledik. Artık kullanmak istediğimiz sort algoritması çeşidini, servis constructor’ına parametre olarak veriyoruz.
Testing, Data Access, MVC Web gibi çeşitli işlevsellik ve büyüklüklerde birçok farklı modülden meydana gelen Spring, temel konseptlerinden biri Dependency Injection olan ve Inversion of Control Container’ı görevi gören bir application framework’üdür.
Basitçe anlatacak olursak, Spring’e uygulamada kullanacağımız farklı işlevler ve katmanlardaki sınıfları (service, controller, entity vs.) ve bu sınıfların bağımlılıklarını belirtiriz. Spring de bizim yerimize implementasyonları gerçekleştirir, yani başka bir deyişle gerekli nesneleri, bu nesnelere gerekli bağımlılıkları enjekte ederek yaratır.
@Service
public class BinarySearchService {
@Autowired
private SortAlgorithm sortAlgorithm;
public int search(int[] ar, int target) {
...
}
}
@Component
public class BubbleSortAlgorithm implements SortAlgorithm {
@Override
public int[] sort(int[] ar) {
...
}
}
@SpringBootApplication
public class SpringApplication {
public static void main(String[] args) {
SpringApplication.run(SecurityappApplication.class, args)
BinarySearchService service = new BinarySearchService()
int target = service.search(arrayToSearch, 5);
}
}
Annotation’ları kullanarak Beanleri tanımladık ve yine Autowired annotation’ı kullanarak bağımlılığımızı belirttik. Spring geri kalan işi arka planda kendi halletti. Dikkat edilirse service sınıfında constructor yazmamıza dahi gerek kalmadı. Çünkü Spring arka planda setter methodu kulanarak dependency’i inject etti. Servis nesnesini herhangi bir sort algorithm parametresi göndermeden yarattık ve direkt olarak kullanıma hazır hale geldi.
Annotationlar hakkında daha fazla bilgi için: https://springframework.guru/spring-framework-annotations/