25 Ağustos 2013 Pazar

Unity Dependency Injection And Inversion Of Control

Uzun zamandır "Dependency Injection and Inversion Of Control" konusunda bir çok kaynak okudum.

Esnek tasarlama,esnek kod geliştirme, sınıfların birbirleri olan bağımlıklarının en aza indirgenmesi vs kulağa hoş geliyordu.Lakin anlatılanlarla gördüğüm örneklerin uyuşmaması sonrasında,olm ismail sen bir örnek proje hazırla,koy bir yere dursun hem millete faydan olsun,hem de lazım oldukça kendinde öğrenirsin dedim kendime... :)

Hangi konuda olursa olsun,bir çok şey ihtiyaç üzerine gelişiyor ve var oluyor.
Bu durum yazılım dünyasında frameworklerde de böyle işte.
Peki bu frameworkler neden çıkmış olabilir acaba ?
Herifin birinin işi,gücü yokmuşta dur şu frameworkü yazayım millette kullansın mı demiş ? Tabi ki hayır...

Dependency Injection(Bağımlıkların Enjekte Edilmesi)

Bakalım Neden Çıkmış :)
Zaman içersinde develer tellal iken,pireler berber iken ,yazılımcıların  çalıştığı projelerde sürekli değişiklik olurmuş.Bu değişiklikleri ne istediği belli olmayan,ama hep ne istediğini bilmeyen müşteriler sebeb olurmuş.Bu değişikler sonucunda projedeki kodlar,çarşafçılarda ki çarşaflardan daha karışık bir hal alır ve yönetimi,bakımı,zorlaşırmış...

Yapılan değişikliker sonrasında,projenin akla bile gelmeyen yerleri etkilenir ve hatalı çalışmasına neden olurmuş.Çünkü yazılımcıların yazdığı sınıflar birbirine gönülden bağlıymış.
Onları birbirinden ayırmak mümkün değilmiş(!)...
Mevcut bir sınıfı alıp,düzeltip elden geçirmeden başka bir projede kullanmak bile mümkün değilmiş...
Çıkış nedenlerinden birisi buydu :)

Inversion Of Control(Kontrolün Tersine Çevrilmesi)

Diğeri ise yazılımda nesne oluşturmak,oluşturulan bu nesnenin takibini yapmak ise yine zor bir iştir.Nerde oluşturdum. Nerde bellekten sildim.Veya bir başka methodun benim oluşturduğum nesneyi silmesi vs...

Hal böyle olunca isyan bayrağını çekip,ulan nesne oluşturmak benim işim mi ?
Sorusunu soruyorsunuz kendize...
Misal ; Lokantaya gidip yiyeceğiniz yemeği kendiniz mi yapıyorsunuz ? Hayır.
Olm şşt bak bakem buraya şipariş vericez diyorsunuz.Ne yemek istediğinizi söylüyorsunuz oda getiriyor.

Yani yiyeceğiniz yemeğin(nesne) çeşidini(tipini),adını söylüyorsunuz o da size yemeği create ettirip getiyor.Yemeğinizi yiyip(nesneyi kullanıp) gidiyorsunuz.Nereye gidiyorsunuz olm önce hesabı ödeyin :)
2. nedeni de budur.

Yani nesne create etme kontrolünün bizden alınarak bu işin framework tarafından yapılması kısmına da inversion of control deniyor.
Yani lokantada yiyeceğimiz yemeğin tipini söyleyip,yiyeceğimiz yemeği istiyoruz.

Şimdi bu kadar hikayeden sonra bağımlıkların enjeksiyonu, kontrolün tersine çevrilmesi işi c# da unity ile nasıl oluyormuş ona bakalım.

Unity için framework için gerekli paketi aşağıdaki gibi kurabilirsiniz.


Gelen pencereden unity yazdıktan sonra ;


Bende kurulu olduğu için install butonu gözükmüyor.
Sizde kurulu değilse "install" butonuna tıklayarak kurabilirsiniz.

indireceğiniz örnekte 3 adet proje bulunmakta.

UnitySample : Domain ile ilgili sınıfların bulunduğu proje

UnitySampleUnitTest : Unit Test Projesi.

UnityWindowsFormApplication : windows form örnek uygulamasıdır.

Stock klasöründe StockCard ve StokCategory sınıfları bulunmakta.Eğer projemizde ki  stok kartlarını,kategorileyecek olursak,böyle bir tasarım yeterlik olacaktır.

Yani burdan şunu anlıyoruz ki ;
Her stok kartının, birde stok kategorisi olacak.
Stok kategorisine ait birden fazla özellik olabileceği için de StockCategory isminde ayrı bir sınıfta topladık.

Yani her StockCard sınıfın içersinde,birde StockCategory tipinde bir nesne olması gerekli değil mi ?

    // stok kategori sınıfı
    public class StockCategory : IStockCategory
    {
        public int id { get; set; }
        public string categoryName { get; set; }
        public DateTime insertionTime { get; set; }
    }
 
   // Stok Kartı sınıfımız
    public class StockCard : IStockCard
    {
        public int id { get; set; }
        public string stockCode { get; set; }
        public string stockName { get; set; }
        public double unitPrice { get; set; }
        public double amount { get; set; }
        public double total { get; set; }
        public DateTime insertionTime { get; set; }


        public StockCategory stockCategory { get; set; }


        public StockCard()
        {

        }
    }
Peki direk olarak StockCard sınıfa StockCategory tipinde bir property eklersek ne olur ? StockCard sınıfı,StockCategory sınıfına gönülden bağlı olur. StockCategory sınıfının ismini değiştirdiğimde,veya projeden sildiğimde derlemez bile... Sınıfın kendini yazmak yerine,O sınıfın implement ettiği interface tipinizi(IStockCategory) yazıyoruz. Böylece StockCategory sınıfını projeden silinse veya ismi değişse bile derlemede problem çıkmayacak. Sırf derlemede problem çıkmasın diye mi böyle yazdık ? Hayır. Neden ?
    public interface IStockCategory
    {
        int id { get; set; }
        string categoryName { get; set; }
        DateTime insertionTime { get; set; }
    }


    // stok kategori sınıfı
    public class StockCategory : IStockCategory
    {
        public int id { get; set; }
        public string categoryName { get; set; }
        public DateTime insertionTime { get; set; }
    }
 
 
 
    public interface IStockCard
    {
        int id { get; set; }
        string stockCode { get; set; }
        string stockName { get; set; }
        double unitPrice { get; set; }
        double amount { get; set; }
        double total { get; set; }
        DateTime insertionTime { get; set; }

        [Dependency("stokCategory")]
        IStockCategory stockCategory { get; set; }
    }


    // Stok Kartı sınıfımız
    public class StockCard : IStockCard
    {
        public int id { get; set; }
        public string stockCode { get; set; }
        public string stockName { get; set; }
        public double unitPrice { get; set; }
        public double amount { get; set; }
        public double total { get; set; }
        public DateTime insertionTime { get; set; }

        public IStockCategory stockCategory { get; set; }


        public StockCard()
        {

        }
    }
Eğer StockCard sınıfına yazacağımız property aşağıdaki gibi yazılmış olursa;
 public IStockCategory stockCategory { get; set; }
IStockCategory sınıfını implement eden her türlü sınıf tipindeki değişken ,stockCategory propertisine atanabilir. alsana esneklik :D Buraya kadar sadece interface'lerle esnek bir tasarım ve modelleme yaptık.

Şimdi geldik zurnanın deliğinin zırt dediği yere !

Bu oluşturduğumuz tiplerleri UnityContainer sınıfının register methodları ile sınıfların meta bilgilerini kayıt ettirip ilgili map'lemeleri en başta bir kere yapıyoruz.

Daha sonra bir nesne lazım olduğu zaman,UnityContainer sınıfından şu tipte bir nesne lazım dedikten sonra istediğimiz nesneyi elde ediyoruz.

ContainerManager sınıfımız aşağıdaki gibi.
using Microsoft.Practices.Unity;

public class ContainerManager
{
    private static UnityContainer container = new UnityContainer();

    public static UnityContainer getContainer()
    {
        return container;
    }
}

Initialization sınıfımız.
using Microsoft.Practices.Unity;
using UnitySample;

public class Initialization
{
    public static void registerObjects()
    {
        UnityContainer container = ContainerManager.getContainer();
        container.RegisterType();

        // StockCard sınıfının,stockCategory property değeri dinamik olarak set ediliyor.
        var injectMember = new InjectionProperty("stockCategory", container.Resolve());
        container.RegisterType(injectMember);
    }
}
Initialization.registerObjects(); ile önce meta bilgileri register edeceğiz. Burada dikkat etmemiz gereken injection olayının(property injection) yapıldığı yerdir.
        // StockCard sınıfının,stockCategory property değeri dinamik olarak set ediliyor.
        var injectMember = new InjectionProperty("stockCategory", container.Resolve());
        container.RegisterType(injectMember);
Görüldüğü üzere dinamik olarak,StockCard sınıfının stockCategory propertiesi set ediliyor. Yani stockCard nesnesini kullanırken ;
  stockCard.stockCategory = new StockCategory();
  stockCard.stockCategory.categoryName = "Beyaz Eşya"; 
yukarıdaki gibi nesne oluşturmanıza gerek kalmayacak.Direk olarak aşağıdaki gibi kullanabileceksiniz.
  stockCard.stockCategory.categoryName = "Beyaz Eşya"; 
Çünkü daha önce containere eklerken,gerekli map'leri yaparak ekledik.Biz yapacağımız işi en başta bir gerek yaptık yani !

Şimdi Artık Herşey Hazır Bir Test Sürüşüne Çıkalım :)

using System;
using Microsoft.Practices.Unity;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using UnitySample;

namespace UnitySampleUnitTest
{
    [TestClass]
    public class StockCardTest
    {

        UnityContainer um;

        [TestInitialize]
        public void testInitialize()
        {
         // sınıfların meta bilgilerini container'a ekleyen methodu çağırıyoruz.
           Initialization.registerObjects();
           um = ContainerManager.getContainer();
        }
         

        [TestMethod]
        public void stokKartiNesnesiOlustur()
        {
            if (!um.IsRegistered())
                throw new Exception("stok kartı register edilmemiş.");

            // bir tane stok kartı nesnesi oluştuyoruz.
            var stockCard = um.Resolve();
            // veya aşağıdaki şekilde de containerdan objecte erişmek mümkün.
            //var stockCard = this.getDIContainer().Resolve();
            stockCard.stockCode = "ST0001";
            stockCard.stockName = "Buzdolabı";
            stockCard.insertionTime = DateTime.Now;
            stockCard.unitPrice = 2500;
            stockCard.amount = 60;

            stockCard.stockCategory.categoryName = "Beyaz Eşya";
            stockCard.stockCategory.id = 1;
            stockCard.stockCategory.insertionTime = DateTime.Now;
        }
    }
}
Bu tip kütüphanelerin temelinde reflection kavramının yattığını bilmekte fayda var ;)
Bu Kadar !


Proje kaynak kodu  
https://github.com/ismailkocacan/Unity-Dependency-Injection-And-Inversion-Of-Control

Kaynaklar 

http://unity.codeplex.com/
http://msdn.microsoft.com/en-us/library/ff649614.aspx
http://msdn.microsoft.com/en-us/library/vstudio/bb383977.aspx