Design Patterns #1: Dependency Injection
Dependency Injection… Evet yine o kadim soru: Nedir?
Bağımlı olunan sınıfların bağımlı olan sınıfın içerisinde new ile oluşturulması dizayn açısından problemlidir. Zira new ile oluşturulan sınıfta her değişiklik yapıldığında kaynak kodun refactor edilmesi gerekir. Bunun önüne geçmek için dependency injection kullanılır. Bağımlı olunan sınıfların newle oluşturulmak yerine constructor parametresi olarak verilmesi veya setter methodu ile atanması yollarıyla yapılmaktadır.
Fantastik IoC konteynırları nedir ve nerede bulunurlar?
.Net projelerinde dependency injection için IoC (Inverse of Control) denilen bir prensip kullanılır. Bu prensibe göre uygulamada bağımlı olunan sınıfları içeren bir container oluşturulur ve bağımlılıklar bu container üzerinden yürütülür.
Container içerisindeki sınıflar 3 farklı şekilde kullanılabilir:
- Singleton : Konteynır içerisinden çağrılan sınıfa ait obje aynıdır. Yani o sınıfa ait tek bir obje üretilmiştir ve konteynırdan o sınıfa ait objeyi isteyen herkese aynı obje dönülür.
- Scoped : Her istek için ayrı bir nesne üretir ve o request pipeline’ında olan tüm isteklere o nesne gönderilir.
- Transient : Her isteğe karşılık bi obje üretir ve gönderir.
.Net projeleri içerisindeki Startup.cs
dosyası içerisindeki public void ConfigureServices(IServiceCollection services){...}
methodunda IoC container’i olusturulur. Örnek üzerinden gidelim,
public void ConfigureServices(IServiceCollection services)
{
// singleton
services.Add(new ServiceDescriptor(typeof(ConsoleLog),new ConsoleLog()));
services.AddSingleton<ConsoleLog>();
// transient
services.Add(new ServiceDescriptor(typeof(TextLog),new TextLog(),ServiceLifetime.Transient));
// scoped
services.AddScoped<ConsoleLog>();
// transient
services.AddTransient<ConsoleLog>();
}
Fonksiyon içindeki ilk 2 satır esasen aynı işi yapmaktadır. services.Add
fonksiyonu default olarak Singleton stratejisini kullanır. Scoped veya transient stratejileri kullanılmak isteniyorsa 4. satirda örneği verildiği gibi üçüncü bir parametre ile belirtilir. AddSingleton
ise isminde anlaşılacağı üzere Singleton stratejisi için özelleştirilmiş bir methoddur. Kullanım açısından Add fonksiyonuna göre de daha kolaydır. AddSingleton
yukarıda örneği verilen şekilde kullanılırsa, parametre vermediğimiz için objeyi sınıfın constructoruna parametre vermeden oluşturmaya çalışacaktır. Eğer singleton olarak eklemek istediğimiz sınıfın constructor’una parametre vermek gerekiyorsa,
services.AddSingleton<ConsoleLog>(p => new ConsoleLog(5));
şeklinde parantez içerisinde parametreler verilerek obje manuel olarak oluşturulur.
Aynı şekilde AddScoped ve AddTransient fonksiyonları da isimlerinden anlaşılacağı üzere scoped ve transient stratejilerini kullanmaktadır.
Konteynır’a sınıf tipi değil interface enjekte etmek
Otomobil ve otobüs sınıflarımız olduğunu düşünelim. Otomobili konteynıra singleton olarak eklemek istiyoruz fakat bir gün otomobil yerine otobüs sınıfını da kullanmamız gerekebileceğini varsayalım. Bu durumda yapacağımız şey, her iki sınıfın da kendisinden türediği bir interface oluşturup, IoC konteynırına injection yaparken o interface tipini vermek. Örnek üzerinden gidelim:
public interface ILog
{
public void Log();
}
public class ConsoleLog : ILog
{
public ConsoleLog(int? a)
{
...
}
public void Log() {
...
}
}
public class TextLog : ILog
{
public TextLog(int? a)
{
...
}
public void Log() {
...
}
}
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ILog>(p => new ConsoleLog());
services.AddScoped<ILog,TextLog>();
}
Yukarida görüldüğü üzere ConsoleLog ve TextLog sınıfları ILog interface’ini implement etmektedir.
services konteynırına bu sınıfları eklerken önceki gibi sınıfların kendi tipini değil implemente ettikleri bu interface’in (ILog) ismini verdik. Böylece konteynırın kullanıldığı kısımda hangi sınıftan olduğuna bakılmaksızın o interface’in ismiyle obje talebinde bulunacak. ILog’u implemente eden tüm sınıflar aynı fonksiyonları içerdiğinden dolayı kaynak kodda bir değişiklik yapmadan, yukardaki eklenen sınıf değiştirilerek bağımlılıklar artık burada yönetilebilir olacak. Yani örneğin Scoped olarak eklenen textlog yerine günün birisinde ConsoleLog sınıfı kullanılmak istenirse,
...
// services.AddScoped<ILog,TextLog>(); => services.AddScoped<ILog,ConsoleLog>();
services.AddScoped<ILog,ConsoleLog>();
...
şeklinde sınıfın isminin değiştirilmesi yeterli olacaktır.
İşte IoC(Inversion of Control) ismi tam olarak bu mantıktan yola çıkarak konulmuştur. Zira görüldüğü gibi artık bağımlılık kontrolü bağımlı olan sınıfta değil konteynırın oluşturulduğu kısımda yapılmaktadır.
NOT: services.AddScoped<ILog,TextLog>();
satırında görüldüğü üzere, konteynıra eklenmek istenen sınıfın objesini manuel oluşturmaya gerek yoktur, sadece isminin verilmesi yeterlidir. Tabii ki bu şekilde bir kullanım için konteynıra eklenen sınıfın constructor’unun zorunlu parameter almaması gerekmektedir. Eğer alıyorsa üst satırlardaki AddSingleton fonksiyonunda olduğu gibi obje manuel oluşturulur.
Konteynırı Contoller’da kullanmak
public class HomeController : ControllerBase
{
readonly ILog _log;
public HomeController(ILog log) {
_log = log;
}
public IActionResult index()
{
_log.Log();
return View();
}
}
işte bu şekilde kullanılır.
Controller bazlı değil action bazlı konteynır kullanmak
[FromServices]
yardımı ile yapılmaktadır.
public class HomeController : ControllerBase
{
public HomeController(ILog log) {
}
public IActionResult index([FromServices]ILog log)
{
log.Log();
return View();
}
}
Leave a comment