kamer.dev

Spring Framework | IoC ve DI

- #java - #spring

Merhaba. Bu yazıda IoC ve DI olarak kısaltılan inversion of control ve dependency injection prensiplerinden ve bunların avantajlarından bahsedeceğim. Bu yazıyı ve Spring ile alakalı diğer yazıları yazarken kullanacağım birincil kaynak spring.io adresinden ulaşabileceğiniz gayet açıklayıcı hazırlanmış dokümantasyon olacaktır. Diğer kullandığım kaynaklar ise yazıların sonlarında istisnasız belirtilecektir.

IoC (Inversion of Control) ve DI (Dependency Injection)

Öncelikle bu kelimelerin sözlük anlamlarını irdeleyelim. Inversion of Control prensibi, kontrolün tersine dönmesi şeklinde ifade edilebilir. Dependency injection ise bağımlılıkları enjekte etmek demektir. Peki bu iki garip kelime grubu bize yazılım geliştirirken nasıl bir fayda sağlıyor?

Bu prensipleri ve kelime manalarını bir kenara bırakıp kendi tecrübelerinizle düşünün. Bir yazılım geliştirirken programın kontrolü kimdedir? Mesela veritabanı bağlantıları, kullandığınız JDK sürümü, veritabanı bağlantısı açmak için kullandığınız driverlar… Genellikle bir XML dosyası kullanarak veya başka yöntemler kullanarak akışa etki edecek tüm parametreleri belirler ve o şekilde çalıştırırız. Daha farklı bir ifadeyle lineer bir program akışı hazırlarız. Hatta tüm bunlar bir yana String tipinde nesne oluşturma kontrolü bile sizdedir. IoC prensibinin hedeflediği şey ise bundan biraz daha farklıdır. Bu akışın doğrudan programcıdan alınıp başka aktörlere devredilmesini amaçlar. Bu aktörler bir framework, servis veya başka herhangi bir bileşen olabilir. Ama temelinde hepsi program akışının lineer olmaması amacını güder. Peki kontrolü devrettiğimizde bunun bize nasıl bir faydası var? Bu prensip, yazılımda ‘loose-coupling’ olarak geçen başka bir prensibi başarmamızı sağlar. Loose-coupling Türkçe’de “gevşek bağlaşım” olarak karşılaştığım bir terim. Bir nesne yaratırken yaratılan bu nesnenin class’lara veya diğer başka bileşenlere olan bağımlılığının en aza indirilmesi prensibidir. Böylece daha esnek bir kod yazma imkânı elde edilir ve modülerlik sağlanır.

Dependency Injection kavramı ise birçok yerde IoC ile eş anlamlı olarak geçse de kapsam bakımından IoC’den daha dardır. IoC’yi uygulamanın birçok yolu vardır, DI ise bunlardan biridir.

DI yaklaşımında ise temel prensip kodumuzda birbirine bağımlı olan bileşenleri tespit edip, bu bağımlılıkları birbirinden ayırıp bağımsız hale getirmek ve bağımlılıklarını dışarıdan enjekte etmektir. Şimdi yukarıda örnekler vererek anlattığım şeyi bir paragrafta kısaca toparlayayım.

Yazılım geliştirme süreçlerinde modülerliği sağlamak için birçok prensip kullanırız. IoC bunlardan biridir. Kontrolü kodu yazan kişiden alıp başka bir aktöre (mesela framework) teslim etmek demektir. Biz kontrolü teslim ettiğimizde loose-coupling sağlarız. Yani bir nesneyi yaratırken bağımlılıklarını minimuma indirmeyi ve bu bağımlılıklarını da gevşek bağlı yapmaya çalışırız. Peki bu prensipleri uygulamak için ne yaparız sorusunun cevaplarından biri ise dependency injection. Yani bağımlılıkları tespit edip ayırmak ve bu bağlılıkları dışarıdan enjekte etmek. Gördüğünüz üzere birbiriyle iç içe geçmiş üç ayrı kavramdır ve kapsamlarını iyi bilmek gerekir. Şimdi bu soyut tanımları biraz daha somulaştıralım.

IHayvan adında bir interface’imiz olsun. Bu interface yalnızca hayvan sesi çıkarmak için kullanılan sesCikar() metodunun imzasını barındırsın.

public interface IHayvan {
    public void sesCikar();
}

Köpek ve Kedi hayvanlarına ait iki tane de class tanımlayalım.

public class Kopek implements IHayvan {

    @Override
    public void sesCikar() {
	System.out.println("Hav hav hav");
    }
}
public class Kedi implements IHayvan {

    @Override
    public void sesCikar() {
	System.out.println("Miyavv");
    }
}

Hayvan seslerini kullanmak için de bir tane servis tanımlayalım.

public class HayvanService {
    private Kopek hayvan1 = new Kopek();

    public HayvanService() {
	hayvan1.sesCikar();
    }
}

Yukarıda görmüş olduğunuz tasarımda Kopek class’ına ait olan hayvan1 nesnesi tight coupled olarak tanımlanmış. Yani hayvan1 nesnesinin kedi olmasını istesek doğrudan kodda değişiklik yapmamız gerekecek. Bu bize esnek bir yazılım ortamı sunmuyor. Şimdi bu servisi DI ile loose-coupled yapalım.

public class HayvanService {
    IHayvan hayvan1;

    public HayvanService(IHayvan hayvan1) {
	this.hayvan1 = hayvan1;
	hayvan1.sesCikar();
    }
}

Değişikliği, yaptığımız DI tanımıyla şöyle açıklayabiliriz. İlk serviste hayvan1 nesnesi Kopek class’ına bağımlıydı. sesCikar() metodunun çağırılması için bu nesnenin Kopek classının sesCikar() metodunu bilmesi gerekiyordu. İkinci örnekte ise bu bağımlılığı tespit edip kaldırdık. Yerine iki classı da kapsayacak olan “private IHayvan hayvan1” tanımlaması yaptık. Dolayısıyla şimdi istersek Kedi, istersek de Kopek classını gönderip sesCikar() metodunu çağırabiliriz.

private void testiGerceklestir() {

	// Bir hayvan tanımladık ama
	//ne olduğunu seçmedik.
	IHayvan hayvan1;

	// Hayvanı burada kedi olarak seçtim.
	hayvan1 = new Kedi();
	hayvan1.sesCikar();

	// Burada ise köpek.
	hayvan1 = new Kopek();
	hayvan1.sesCikar();

}

Bu kodun çıktısı aşağıdaki gibi olacaktır:

Kodların GitHub linki: https://github.com/kamer/IoCveDI

Yararlandığım Kaynaklar

Bu yazı ilk olarak https://medium.com/topluluk/spring-framework-1-ioc-ve-di-3e2da31bc0e5 adresinde yayınlanmıştır.