Dependency Injection & Inversion of Control

NOT : Bu yazı Aykut TAŞDELEN’in C++ Java ve C# ile UML ve Dizayn Paternleri kitabından sınırlı bir alıntıdır izinsiz kullanılıp alıntı yapılamaz ! Konunun devamı söz konusu kitapta yer almaktadır.

c++ java ve c# ile uml ve dizayn paternleri kitap

İlk Türkçe C++ ile Dizayn Paternleri kitabı çıktı !

7.1 Dependency Injection ve Inversion of Control Nedir ?

7.1.1 Giriş ve Tespit

Bu patern basit bir anlatımla bağımlılıkların (dependency) yönetimiyle ilgilidir. Nesne yönelimlilik paradigmasına göre yazılım geliştirmek demek; varlıkları nesneler ile modellemek, ve bu nesneler arasındaki olası ilişkiler ve iletişime dayalı programlama yapmak anlamına gelir. Bu durumun doğal sonucu nesnelerin birbirlerine bağımlı hale gelmeleridir. Bağımlılık algısı gerçek hayatta olduğu gibi nesne yönelimli programlama için de olumsuzdur. Bağımlılıklar mümkün olduğunca azaltılmalı ve yönetilebilir olmalıdır. Dependency Injection patern’i bu noktada bir çözüm sağlamayı hedefler.

Bir nesne yapması gereken bir işi başka bir nesnenin yardımıyla yapıyorsa ya da o nesne olmadan yapamıyorsa söz konusu nesne diğer nesneye bağımlıdır diyebiliriz. Bu durum sembolik bir kodla örneklemeye çalışırsak;
Sample sınıfı msg veri elemanının değerini Screen sınıfının Display() metodu ile ekranda görüntülemek zorunda ise Sample ile Screen nesneleri arasında bağımlılık kaçınılmazdır.

class Screen
{
public void Display(string s)
{
Console.WriteLine(s);
}
}

class Sample
{
private string msg;

public string Msg
{
get
{
return msg;
}
set
{
msg = value;
}
}

public Sample()
{
this.msg = “”;
}

public void Test()
{
Screen scr = new Screen();
scr.Display(msg);
}
}

Bu tümüyle yanlış bir tasarımdır zira bağımlılık bir soyutlamaya değil de Screen gibi somut bir implementasyona dayalıdır. Bunun bir sonucu olarak beklentiler Sample::msg’nin sadece konsol ekranında değil de bir web sayfasında da görüntülenebilmesi yönünde değişim gösterirse ne olacaktır ? İşte kötü bir tasarım daha :

class Screen
{
public void Display(string s)
{
Console.WriteLine(s);
}
}

class Page
{
public void Display(string s)
{
Response.Write(“” + s + “”);
}
}

class Sample
{
private string msg;

public string Msg
{
get
{
return msg;
}
set
{
msg = value;
}
}

public Sample()
{
this.msg = “”;
}

public void Test()
{
//Screen scr = new Screen();
//scr.Display(msg);

Page pg = new Page();
pg.Display(msg);
}
}

Page gibi bir sınıf daha yapıya eklenilip bu sınıf türünde yaratılacak bir nesneyle (pg) Test metodu yeniden düzenlenir. Bu tasarım da yanlıştır çünkü Sample ile Screen ve Page nesneleri arasındaki bağımlılık çok sıkıdır. Bu bağımlılığı bir soyutlama yaparak -IDisplayer arayüzü sayesinde- tabir yerindeyse biraz gevşetelim;

interface IDisplayer
{
void Display(string s);
}

class Screen : IDisplayer
{
public void Display(string s)
{
Console.WriteLine(s);
}
}

class Page : IDisplayer
{
public void Display(string s)
{
Response.Write(“” + s + “”);
}
}

class Sample
{
private IDisplayer displayer;
private string msg;

public string Msg
{
get
{
return msg;
}
set
{
msg = value;
}
}

public Sample()
{
this.msg = “”;
}

public void Test()
{
displayer.Display(msg);
}
}

Bu durumda da displayer nesnesi henüz yaratılmadığı için sorun çıkacaktır. Bu sorun birkaç farklı yolla çözülebilir :

1. Method Injection : Test() metodunun IDisplayer türünde alacağı bir parametre ile.
2. Constructor Injection : Sample constructor’ının IDisplayer türünde alacağı bir parametre ile.
3. Setter Injection : Sample sınıfına eklenecek IDisplayer türünde bir property ile.

Bu üç yöntemden hangisi kullanılırsa kullanılsın aslında yapılacak olan şey; Sample sınıfının bağımlığı olduğu referansı ona dışarıdan enjekte etmektir. Örneğin 2. yöntem uygulanırsa;

interface IDisplayer
{
void Display(string s);
}

class Screen : IDisplayer
{
public void Display(string s)
{
Console.WriteLine(s);
}
}

class Page : IDisplayer
{
public void Display(string s)
{
Response.Write(“” + s + “”);
}
}

class Sample
{
private IDisplayer displayer;

private string msg;

public string Msg
{
get
{
return msg;
}
set
{
msg = value;
}
}

public Sample()
{
this.displayer = null;
this.msg = “”;
}

public Sample(IDisplayer displayer)
{
this.displayer = displayer;
}

public void Test()
{
displayer.Display(msg);
}
}

// Test kodu
class Program
{
public static void Main()
{
Sample smp = new Sample(new Screen());
smp.Msg = “Android Programcıları Derneği”;
smp.Test();
}
}

Bu öncekilere izafeten doğru bir tasarım sayılabilir zira Screen nesnesi Sample nesnesine dışarıdan enjekte edilmiş bir bağımlılıktır ancak yine de hatalıdır. Çünkü konsol ekranı yerine sayfa üzerinde çıktı oluşturulmak istenirse kaynak kodda aşağıdaki gibi bir değişiklik ve yeniden derlemeye ihtiyaç vardır :

Sample smp = new Sample(new Screen());

Yerine;

Sample smp = new Sample(new Page());

Öte yandan kullanıcı smp nesnesini default constructor (varsayılan başlangıç fonksiyonu) ile yaratmak isterse derleme sırasında değilse bile çalışma zamanında hataya yani exception oluşmasına sebep olabilir. Bu noktada doğru bir tasarımdan beklentiler şunlardır :

1. Bağımlılıkların somut implementasyonlara değil soyutlamalara dayalı olması.
2. Nedeni bağımlılıklar olan çalışma zamanı hatalarının önüne geçmek.
3. Bağımlılıkların dışarıdan enjekte edilebilmesi.
4. Kaynak kodda değişikliğe ve yeniden derlemeye ihtiyaç duyulmaması.

Bu beklentileri karşılayacak çözüm harici bir konfigürasyon dosyasıyla bağımlılıkları belirlemektir.

[Library.dll]
using System;
using System.Data;

namespace Library
{
public interface IDisplayer
{
void Display(string s);
}

public class Screen : IDisplayer
{
public void Display(string s)
{
Console.WriteLine(s);
}
}

public class Page : IDisplayer
{
public void Display(string s)
{
Response.Write(“” + s + “”);
}
}

public class Sample
{
private IDisplayer displayer;

private string msg;

public string Msg
{
get
{
return msg;
}
set
{
msg = value;
}
}

internal Sample()
{
this.displayer = null;
this.msg = “”;
}

internal Sample(IDisplayer displayer)
{
this.displayer = displayer;
}

public void Test()
{
displayer.Display(msg);
}
}

public class Context
{
public static Sample createSample()
{
DataSet ds = new DataSet();
ds.ReadXml(@”..\..\..\Dependency.xml”);

IDisplayer dsp = null;

switch (ds.Tables[“sample”].Rows[0][“displayer”].ToString())
{
case “screen”:
dsp = new Screen();
break;
case “page”:
dsp = new Page();
break;
}

return new Sample(dsp)
{
Msg = ds.Tables[“sample”].Rows[0][“msg”].ToString()
};
}
}
}

[DependencyInjection.exe]
using System;
using Library;

namespace DependencyInjection1
{
class Program
{
public static void Main()
{
Sample smp = Context.createSample();
smp.Test();
}
}
}

[Dependency.xml]

<?xml version=”1.0″ encoding=”utf-8″ ?>

<config>

<dependency>

<sample
msg=”Android Programcıları Derneği”
displayer=”screen”
/>

</dependency>

</config>

Bu tasarımda uygulama bir class library ve console uygulaması şeklinde ikiye ayrılmış ve Sample sınıfının constructor’ları internal hale getirilmiştir. Ayrıca yapıya Context isimli bir sınıf eklenmiş ve bu sınıfın createSample() metodu ile Sample nesnesinin yaratılmasını zorunlu olmuştur.

createSample() metodu incelenirse Dependency.xml dosyasının parse edilerek yani çözümlenerek içindeki belirlemelere göre Sample nesnesini yarattığı görülecektir. Bu sayede Sample nesnesinin bağımlılıkları tümüyle dışarıdan enjekte edilir hale gelmiştir.

7.1.2 Inversion Of Control (IoC)

Dependency Injection patern’i literatürde çoğu zaman Inversion Of Control (IoC) prensibiyle birlikte anılır. Daha önceki bölümlerde patern’lerin aslında prensiplerin kalıplaştırılmış halleri olduğundan bahsedilmişti.

Dependency Injection, Martin Fowler isimli ünlü metodolojist tarafından formüle edilmiş ve Inversion Of Control prensibine dayalı bir patern’dir. Süreç içerisinde bu paternin kullanıldığı pek çok framework de geliştirilmiş olup en ünlüleri şunlardır :

• Spring ve Spring.NET
• NInject
• Unit
• Castle Windsor
• Structure Map

Bu frameworklerde genelde bir IocContainer (container = taşıyıcı, barındırıcı) sınıfının varlığı göze çarpar. Temelde IocContainer sınıfların görevi; bağımlılıkları içermek ve konfigüratif bilgilere dayalı olarak bu bağımlılıkların çözümlenmesini sağlamaktır. IocContainer’lar sayesinde uygulama akışını değiştirecek çeşitli nesneleri programcının belirlediği konfigürasyona göre elde etmek kolaylaşır.

Reklamlar