Burada sürekli bir takım yazılımsal kavramlardan ve tasarım kalıplarından bahsetmeye         çalışıyorum. Tabi bunların hepsinin tam olarak ne ifade ettiklerini anlayabilmek         için öncelikle problemleri anlayabiliyor olmak çok büyük bir önem taşıyor. Bu yüzden         elimden geldiğince gerçek hayat örnekleriyle anlatmak istediklerimi örneklemeye         çalışıyorum.
Bu yazıda sizlere sıkça duyduğumuz Dependency Injection ya da başka bir değişle         Inversion Of Control adı verilen kalıptan bahsediyor olacağım. Ancak bunu anlatabilmek         için öncelikle Loose Coupling den bahsetmek istiyorum.
 
Loose Coupling
Önceki yazılarımda nesneye yönelik programlamanın(oop) bir prensibi olan "Single         Responsibility" den ve neden gerekli olduğundan bahsetmiştim. Ancak hızlı bir         özet geçmemiz gerekirse, yazdığımız sınıfların ya             da program parçalarının amaçlarına uygun olarak bölünmesi ve amaçlarının dışında             bir görevi üstlenmemeleri bize yazdığımız bu parçaların başka projelerde ya da mevcut             program içerisinde daha çok kullanılabilmesinde fayda sağlamaktadır. Bu yüzden yapacağımız             tasarımlarda sınıfların daima tek bir sorumluluğu olmalıdır. Böylece bir takım şeyleri             tekrar tekrar kullanmaktan kurtulmuş oluruz ve bu da bize elbette zaman kazandırmış             olur. Single Responsibility'nin bir sonraki adımı olarak da Loose         Coupling düşünülebilir. Yani mümkün oldukça bir sınıf başka sınıflara bağımlı olmamalı,         bunun yanında da tek bir amaca hizmet etmelidir. Burada bağımlılıktan kastımı biraz         daha açıklamak istiyorum. Diyelim ki bir sınıfınız var. Bu sınıfınızda bir takım         işlemler yapıyorsunuz. Örneğin bu sınıf girdi olarak bir nesne alıyorsa, sizin bu         sınıfınız bu nesneye bağımlıdır. Çünkü bu nesne olmadan sınıfınızın bir anlamı yoktur.         Benzer şekilde, girdi nesnesinin peşindeki diğer nesneler de bağımlılığı arttıracaktır.         Bunun bir dezavantajı olarak yazdığınız sınıf da bağımlı olduğu nesnelerin değişmesi         durumunda bu değişiklikten etkilenecektir.
Örnek bir entegrasyon senaryosu
Biraz daha konuya ısınmaya başladığımıza göre basit bir ihtiyaç hakkında konuşmaya         başlayalım; Müşteriniz ile bir projeye başladınız. Yapmaya çalıştığınız şey ise         basit bir stok programı. Müşterinizin ihtiyacına göre yaptığınız analiz doğrultusunda         stok bilgilerini, müşterinizin envanter kayıtlarını sakladığı bir yazılımın veritabanından         almanız gerekiyor. Ancak veritabanına erişime izin vermiyor da bu yazılım firması         size bir web servis ile bu verileri verebileceğini söylüyor. Siz de bunu göz önüne         alarak bu altyapı üzerine projenizi yapmaya başlıyorsunuz. Bir zaman sonra, henüz         projeniz bitmeden, karşı taraftaki firma web servisi kapatıyor ve daha ileri bir         teknoloji olan WCF üzerinden yayın yapmaya başlıyor. Tabi size de bunu bildirdiğinde         siz gidip kodunuzu tekrar değiştiriyorsunuz. Bir zaman sonra projenizi teslim ediyorsunuz         ve tamamlamış oluyorsunuz, ya da siz öyle sanıyorsunuz. Aradan bir kaç ay geçiyor         ve müşteriniz size envanter kayıtlarını tutan programı değiştirdiklerini, eski firma yerine         başka bir firma ile çalıştıklarını ve verilerin hepsini yeni yazılımın veritabanına         taşıdıklarını söylüyor. Bunun doğal sonucu olarak sizin de verilerinizi artık oradan almanız gerekiyor.         Ancak bu sefer durum biraz daha farklı çünkü oradaki firma ihtiyacınız olan verileri         sizin veritabanınıza belli aralıklarla taşıyabileceğini söylüyor ve sizin de verileri buradan         almanız gerektiğini anlatıyor. Bu durumda yine kolları sıvayıp koda girişiyorsunuz.
Yukarıdaki senaryo eminim aşağı yukarı benzer de olsa pek çok yazılımcının başına         gelmektedir. Bu durumda nasıl bir mimari tasarlanmalı ya da nasıl bir yol izlenmeli?
İşte burada işin içine Dependency Injection(DI) dediğimiz kavram giriyor. Yukarıda         da anlattığım gibi DI'in en ciddi gereksinim haline geldiği zaman bağımlılıkların         yönetilmesi anıdır. Yukarıdaki senaryoda yazılan programın ana modulü stok verisinin         haricinde veriyi nasıl alacağına da odaklanırsa yapılan iş içinden çıkılamayacak         bir hal alır. Halbuki düşünüldüğü zaman yazılan programın yapması gereken iş ile         veriyi nereden aldığının hiç bir ilişkisi yoktur. Burada asıl önemli olan, getirilen         veridir ve programın çekirdeğinin de sadece gelecek olan veriyi bilmesi gerekmektedir.         Bu durumda programın çekirdeğindeki verilere ihtiyaç duyan sınıfın tek bilmesi gereken         bir adaptör nesnesinden veriyi kendi bildiği şekilde alacak olmasıdır. Bundan         sonra da veri web servisten mi gelmiş, WCF servisten mi gelmiş yoksa bir veritabanından         mı alınmış gibi konularla ilgilenmesine ihtiyacı olmayacaktır.
Şimdi yukarıdaki maddeleri göz önüne alarak bir tasarım yapmaya çalışalım. Bu tasarıma         da programın çekirdeğinden başlayalım. Ben burada boş bir solution yaratıp, içerisine         bir adet console application ekliyorum. Programımın çekirdeğinin burası olduğunu         varsayıyorum. Aşağıdaki gibi bir şekilde projemize başlayalım;
 
public class StoreWinApplication         
{         
    private IStoreProvider m_StoreProvider;         
    public IStoreProvider StoreProvider         
    {         
        get { return m_StoreProvider; }         
        set { m_StoreProvider = value; }         
    }         
    internal IStoreInformation GetStoreInformation()         
    {         
        return StoreProvider.LoadInformation();         
    }         
}
Yukarıdaki sınıfa baktığınızda ihtiyacımız olan veriyi çekebilecek olan bir StoreProvider         özelliğine sahip. Bu özellik de IStoreProvider tipinde. Yani bir arayüz olarak dışarıya         açılmış durumda. Aynı zamanda GetInformation() methoduna bakacak olursak bizim IStoreProvider         arayüzünü tanımlayan özelliğimizi, StoreProvider 'ı çalıştırmakta ve karşılığında         yine bir arayüz olan IStoreInformation tipinde bir nesne döndürmektedir. Bunu yaparak         şu anda business nesnemizi verinin alınacağı yerden ayırmış olduk. Böylece  veritabanı, wcf ya da web servis bağımlılığını da kırmış olduk. Bunu kullanırken         de aşağıdaki gibi kullanacağız,
 
static void Main(string[] args)         
{         
    StoreWinApplication application = new StoreWinApplication();         
    application.StoreProvider = new DatabaseStoreProvider();         
    IStoreInformation information = application.GetStoreInformation();         
    Console.WriteLine(information.Name);
}
 
yukarıda da gördüğünüz gibi çalışan sınıfımızın StoreProvider özelliğinin değerini         dışarıdan DatabaseStoreProvider tipindeki ve IStoreProvider arayüzünü tanımlayan         sınıf olarak belirledik. Böylece çalıştırdığımızda da veriyi veritabanından alacaktır.         Böylelikle ilerideki bir zamanda eklenecek olan yeni bir veri kaynağının da bu işleme         dahil olması çok kolay olmuş oldu.
Ancak hala başka bir sorunumuz var. Her yeni eklenen provider için tekrar tekrar         kodumuzu derlememiz gerekiyor. O zaman bunu gönderdiğimizde, müşterinin istediği         şekilde değişiklik yapabilmesi için eklediği her bir sınıfla birlikte bizim kodumuzu         derlemesi gerekmekte. Ve bunları da yine buradan gördüğünüz üzere çalışma anında         düzenleyebilmesi için bir şey yapamamış olduk.
Bu gibi sorunları çözebilmenin elbette bir çok yolu olabilir. Ancak ben bu yazıda         sizlere Unity Application Block 'tan bahsetmek istiyorum.
 
Unity Application Block
Unity http://unity.codeplex.com/ adresinden         erişebileceğiniz bir dependency injection container dır ve Microsoft Patterns&Practices         tarafından geliştirilmektedir. Hakkında kısaca bahsetmek gerekirse, bizim application         config üzerinde (web ise web.config, windows ise app.config) tanımladığımız arayüzlerin         ve o arayüzlere karşılık gelen sınıfların çalışma anında bir kutuya yüklenmesini         ve daha sonra ihtiyaç halinde ilgili arayüz için yüklenen sınıfın istenen kişiye         verilmesini sağlar. Yani yazılımcı ya da müşteri, uygulamanın konfigurasyon dosyasına         sizin belirlediğiniz arayüzünüze (bu uygulamada IStoreProvider) karşılık gelecek         olan sınıfın tanımını yapıyor ve buna göre o arayüz çağrıldığında size o arayüz         için kullanıcının tanımladığı sınıfın örneğini yaratıp verebiliyor. Bu da dikkat         ederseniz, bizim az önce hakkında konuştuğumuz problemin ta kendisi!
Bu ihtiyacımıza yönelik güzel bir çözüm de bulduğumuza göre, unity 'yi biraz         daha incelemeye koyulalım. Öncelikle konfigurasyon dosyasında nasıl bir değişiklik         yapmam gerektiğine bakalım;
 
<?xmlversion="1.0" encoding="utf-8" ?>  
<configuration>
    <configSections>
        <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,
                                                            Microsoft.Practices.Unity.Configuration"/>
 </configSections>  
<unity>         
    <containers>         
        <container>         
            <type type="StoreClientLibrary.IStoreProvider,StoreClientLibrary"
                        mapTo="ServiceStoreProviderLibrary.ServiceStoreProvider, ServiceStoreProviderLibrary"/>         
        </container>          
    </containers>          
</unity>
 
Konfigurasyon dosyasına baktığımızda en üstte yeni bir config section eklememiz          gerektiğini görüyoruz. Burada Unity'nin gerekli bilgileri unity xml düğümünde          okuyacağını bildiriyor. Bundan sonraki kısım ise az önce de bahsettiğim hangi          tip için hangi nesnenin oluşturulacağı bilgisini tutuyor. Burada birden fazla          container yaratıp bu container ların herbirini istediğiniz gibi hem uygulama          ayağa kalkarken hem de çalışma anında değiştirebilmeniz de mümkün. Peki bu          container'ı nasıl kullanacağımıza bir göz atalım;
 
IUnityContainer container = new UnityContainer();         
UnityConfigurationSection unityConfig =         
                 (UnityConfigurationSection)ConfigurationManager.GetSection("unity");         
unityConfig.Configure(container);
 
Öncelikle bir UnityContainer yaratıyoruz. Ardından app.config üzerinden ilgili          container a ait olan konfigurasyon bilgisini yüklüyoruz ve container ımızı uygun          şekilde ayarlaması için configuration section a veriyoruz. Böylece konfigurasyon          dosyasında tanımlamış olduğumuz tüm tipler container içerisine yüklenmiş oluyor.          Bundan sonra aşağıdaki gibi istediğimiz tipi çağırabiliriz,
 
IStoreProvider provider =         container.Resolve<IStoreProvider>();
 
Artık bu container içerisinden istediğimiz tipi kaldırıp yerine istediğimiz tipi          tekrar koyabiliriz. Tabi böylece container ın bilgilerini konfigurasyon          dosyasından çekmek zorunda değiliz. Aksine veritabanında da bunları          saklayabiliriz. Şimdi de refactor edilmiş kod StoreWinApplication sınıfına bir          göz atalım;
 
public class StoreWinApplication         
{         
    private IUnityContainer container = new UnityContainer();         
    private IStoreProvider m_StoreProvider;         
    public IStoreProvider StoreProvider         
    {
        get         
        {
             if          (m_StoreProvider == null)         
            {
                         m_StoreProvider = container.Resolve<IStoreProvider>();         
            }         
            return          m_StoreProvider;         
        }         
        set { m_StoreProvider = value; }         
    }
    public StoreWinApplication()         
    {         
        UnityConfigurationSection unityConfig =         
                         (UnityConfigurationSection)ConfigurationManager.GetSection("unity");         
        unityConfig.Configure(container);         
    }         
    internal IStoreInformation GetStoreInformation()         
    {         
        return string.Format("Store Information:          {0}", StoreProvider.LoadInformation());
    }         
}
 
Burada artık container ımızı sınıfımız ilk ayağa kalktığında yaratıyoruz ve          ondan sonra StoreProvider özelliğini yükleyebilir hale getirip yüklemiyoruz. İlk          çağırım ile birlikte StoreProvider özelliği konfigurasyon dosyasından okunarak          yükleniyor.
Böylelikle Dependency Injection nedir ve nasıl yapılabilir öğrenmiş olduk. Bunun          yanında Unity Application Block ile ilgili olarak basit bir ön bilgiye de sah  ip          olmuş olduk. Tabi ki Unity ile yapılabilecek pek çok şey var. Ancak onu da başka          bir yazıya bırakıyorum. Bu yazıda yapılan örneği de incelemek için buraya          tıklayıp  bilgisayarınıza indirebilirsiniz. Örnek projede Unity'nin son          versiyonunun ihtiyaç duyulan bazı dll leri mevcut. Ancak unity hakkında daha          fazla bilgi almak isterseniz  http://unity.codeplex.com/ adresini          ziyaret edebilirsiniz.