Makale Özeti

Bu makalede dependency injection patternine ve inversion of control yontemine goz atacagiz.

Makale

DEPENDENCY INJECTION (DI) VE INVERSION OF CONTROL (IoC)

Yakin zamanda ilginizi cok daha fazla cekecek bir konu: Dependency Injection ve Inversion of Control. Oncelikle bagimlilik ne demek onu bir irdeleyelim. Dusunun ki blog yazmaya karar verdiniz ve bunun icin BlogService sinifi olusturdunuz. BlogService sinifiniz UserDAO’ya ve BlogDAO siniflarindan orneklenmis nesnelere ihtiyac duysun. Bu durumda BlogService bagimli(dependent), UserDAO ve BlogDAO bagimlilik(dependency) olur.

OOP’nin temel konseptlerinden biri yeniden kullanilabilirlik ve maintainability oldugunu hepimiz biliyoruz. Ayni isi yapan kodlari tekrar tekrar yazmak yerine siniflar olustururuz, gerektiginde bunlardan miras aliriz baska siniflarin icinde tekrar tekrar kullaniriz. Veri katmanimizi siniflara boleriz, servislerin icinde kullaniriz. Bunlar yapmamiz gereken seyler aslinda.  Peki simdi dusunun, UserDAO sinifiniz 10 sinif tarafindan kullaniliyor. Tutun ki bu sinifta refactorization yapmaniz gerekti. UserDAO sinifinin constructorunu degistirdiniz. Kodunuzu derlemeye kalktiniz, ve o anda errorlari almaya baslarsiniz. Ne demistik, kodun duzenlenebilir olmasi bizim temel amaclarimizdan biri. Bu tip sorunlari asmak icin aklima gelen ilk yontem Factory Pattern. Bu patternin esas amaci kalitim iliskisi olan siniflarda sinifin tam adini gizlemek, aslinda ilgili sinifi kendisi secerek ornegini almaktir. Ornek uzerinde inceleyelim:

 public interface ImageReader 
{
     public DecodedImage getDecodedImage();
}
public class GifReader: ImageReader 
{
    public GifReader( Stream in ) 
    {
    }
    public DecodedImage getDecodedImage() 
    {
        return decodedImage;
    }
}
public class JpegReader implements ImageReader 
{

}
public class ImageReaderFactory 
{
        public static ImageReader getImageReader( Stream is )
        {
                int imageType = figureOutImageType( is );
                switch( imageType )
                {
            case ImageReaderFactory.GIF:
                                     return new GifReader( is );
            case ImageReaderFactory.JPEG:
                                     return new JpegReader( is );
                }
        }
}

Java kodundan alintidir.

Gordugunuz gibi Factory sinifimiz gelen Stream’in turune gore ilgili ImageReader’i yaratir ve “is” parametresini bu siniflarin yapici methodlarina gecer. Fakat burada ufak bir sorunumuz var. Hangi siniflarin mevcut oldugunu “kodumuzun” icine elle giriyoruz, bu da bir degisiklik oldugunda kodun yeniden derlenmesi demek aslinda.

FactoryPatternini baska bir tarafa birakalim ve esas konumuz olan Dependency Injection ve IoC’ye donelim. Martin Fowler bu makalesinde olayi anlatiyor fakat bu makaleyi biraz karisik buldum. Ama ustunde yogunlasirsaniz benim anlatacagimdan(Windsor) farkli(Spring, PicoContainer gibi) frameworklerdeki implementasyonlarini gorebilirsiniz.

Dependency Injection aslinda Bagimli sinifin bagimlisi oldugu siniflari kendisinin yaratmamasi demek. Bu nesneler sinifa disaridan saglaniyor baska bir deyisle. IoC ise sinifimizin bagimliliklari runtime olarak incelenmesi ve bagimlisi oldunan nesnelerin genelde bir framework tarafindan yaratilmasi ve sinifa enjekte edilmesi demek. Dependency Injection bu yuzden “holywood pattern” olarak da bilinir(don’t call us, we’ll call you-bizi aramayin, biz sizi arariz). Ben bu makalede cok sevdigim bir frameworku kullanacagim: Windsor. Sonraki makalelerimizde windsorun yararlarini siz de goreceksiniz.

Dependency Injection temelde 2 sekilde yapilir: Constructor Injection ve Setter Property Injection. Kafanizda olusmasi icin

Public class BlogService:IBlogService
{
           private IBlogDAO _BlogDAO;
           private IUserDAO _UserDAO;
           public BlogService(IBlogDAO blogdao, IUserDAO userdao)
           {
                     this._BlogDAO=blogdao;
                     this._UserDAO=userdao;
           }
          //Burada bazi metodlar var.
}

Yukaridaki ornegimiz Constructor Injection. Sinifimizin bagimliliklarini Constructora gectigimiz parametreler araciligiyla cozuyoruz.

public class Shop

{

        StockManager _StockManager;

        string _ShopZipCode;

  public StockManager StockManager

        {

               set{this._StockManager = stockManager;}

               get{return this._ StockManager;}

        }

}

Bu ornegimiz de Setter Property injection. Ikisi de temelde ayni islemlere yariyor gibi gorunebilir fakat ikisinin de kullanim alanlari farkli. Constructor Injection islevi geregi objeyi kullanilabilir hale getirir. Bu durumda hemen objenin temel bagimliliklarini constructor injection yontemiyle almak mantikli olacaktir. Bu yontemin dezavantaji ise inheritanceda cikiyor karsimiza. Inherit eden sinif(cocuk sinif) inherit edilen sinifa(anne sinifa) a de parametrelerin bazilarini saglamak zorunda(cocuk sinif yaratilirken anne sinifin constructurunun da cagirilacagini unutmamaliyiz.). Bu durumda siniflarimiz birbirinden miras aldikca kodumuz biraz daha cirkin gorunebilir. Bu durumda property’leri kullanmis olsaydik propertyler inherit edilecegi icin boyle bir sorunumuz olmayacakti. Fakat bunun dezavantaji sinifimizin her bagimliligini frameworkumuz cozmek zorunda degil ve bu durumda objemiz hala calisabilir durumda. Constructor’da ise bu bagimliliklar saglanmadiginda objemiz gecersiz olacak ve biz bu durumdan exceptionlarla haberdar olabilecegiz.

Ayrica bu tarz bagimliliklarin disaridan verildigi yapilar, testing i kolaylastirir. Amacimiz Shop sinifini test etmek olduguna gore StockManager sinifini ayrica test etmemize gerek yok onun yerine onun mock’unu gecirerek istedigimiz durumu saglatip sadece Shop sinifini test ettirebiliriz.

Makalemizin geri kalanini basit bir Logging uygulamasi ustunde gorelim.

Simdi bos bir solution acin ve Logger isimli class library’yi icine ekleyin.

Loglama islemini yaptirmak icin 3 secenegimiz olsun: Console Uygulamalari icin ekrana bastirmak, Web Uygulamalari icin Trace’e gondermek ve yine kendiliginden genisletilebilir olan RepositoryLogger. Bunun icin ben sadece bir metod tanimindan olusan bir interface yazmayi dusunuyorum. Metodumuz ise tahmin ettiginiz gibi Log(string name, string message, string Exception); bu durumda interface kodumuz

`

Sanirim burada anlatmam gereken pek bir sey yok. Daha once once interface in yaygin kullanimini yapmayanlara Castle Projecti incelemelerini siddetle tavsiye ediyorum. Hatta yaygin kullanimini yapanlar yeni kullanim alanlari gorecektir.

Gevezeligi birakip bu interface’in implementasyonlarina gecelim. Once Consolu uygulamasi icin olani yapalim.

Console’a yazdirmak icin ihtiyacimiz olan tek sey Console sinifini kullanmak. Bu sinifinin Console sinifindan baska bagimliligi yok, aslinda Console sinifi da bir bagimlilik degil cunku Console sinifinin ornegini alinmasina gerek yok cunku static bir sinif. Yaptigimiz tek sey Console.WriteLine(“”) kullanmak. Implementasyonu asagida.

 

Sirada web uygulamalarinin tuketebilecegi Trace  sinifi ile ekrana Trace modu acikken bastirmayi gosterelim. Bu aslinda cok yararli bir yontem. Bunu gerceklestirmek icin oncelikle kutuphanemize System.Web’i ekleyelim.

Implementasyonu yukarida. Bu da esasen ConsoleLogger ile ayni mantikta calisiyor. Simdi herhangi bir repository’ye loglama yapan sinifi yazalim. Bunu dizayn ederken bir tane de bagimliligimiz olsun, Dependency Injection’u kullanmak icin. RepositoryLogger:ILogger sinifimiz olsun. Sonucta DataLogger sinifimizi cesitli veritabanlariyla kullanmak isteyebiliriz.(ornegin oracle, mssql, hatta access). Veritabani yerine de session kullanmak projeyi basit kilmak adina izlemek istedigim yontem. Simdi nasil bir yontem izlememiz gerektigini dusunelim. Ben bir sonraki projelerde de uygulamasi kolay olacagi icin Linq’yu da kullanan bir interface yazdim. Adi IRepository<T>. Generic olmasinin avantaji farkli siniflar icin de kullanabilecek olmasi, yani sonraki makalemilzde MVC anlatirken ornek olarak blog uygulamasi yapacagim ve blogumuzda Entry, Comment, Log gibi birden fazla sinif olacak, bunlar icin ayri ayri sinif yazmak yerine generic yapip tum bu entity icin ayni sinifi kullanacagim. Interface’imizde sorgulama amacli bir de Linq<T>() metodu koydum, bu da bir sonraki makalemizde Nhibernate kullanirken isimizi oldukca kolaylastiracak.

Dedigim gibi veritabani yerine Session kullanacagiz, bunun icin de bir tane SessionRepository<T>:IRepository<T> yazacagim. Sessionda saklama yontemim Repository_TninTipAdi. Birden fazla nesne saklanmasi icin session degiskeninde List<T> ornegini sakliyorum. GetList metodu ile sessionda sakladigimiz List ornegini geri donduruyorum, Linq() sinifinda ise Extension methodlardan AsQueryable’i cagirip linq icin uygun hale getiriyorum.

  

Session depolama sinifini yazdigimiza gore son islem olan RepositoryLogger’i yazalim. Digerlerinden pek bir farki yok.

Gordugunuz gibi bu sinifimin bir bagimliligi var: IRepository<T> tipinden LogRepository. Log methodlarimizda da Log sinifinin ornegini repository’mize ekliyoruz. .net 2.0 dan farkli olarak gordugunuz

Log l=new Log{Date=DateTime.Now,Message=message,Name=name};


seklindeki nesne ilklendirmesi 3.5 ozelligi. Constructor cagirmadan(eger sinifimizin default constructoru(parametresiz constructor) varsa) propertylerini set edebiliyoruz. Gelin bir de Log sinifimiza bakalim.

`

Bildigimiz Entity sinifi, tek farki yine 3.5 ozelligi olan Automatic Properties. Fieldlari kendisi derleme aninda yaratiyor.

Siniflarimizi yarattik, simdi bir de bunlari tuketecek projelerimizi yazalim. Bir consol uygulamasi 2 de web uygulamasi yazalim.

Yeni bir console projesi ekleyelim. Windsoru kullandigimiz icin projemize Castle.Windsor referansini ve windsorun bagimli oldugu Castle.Core ve Castle.MicroKernel projelerinin referansini eklememiz gerek. Bunu ekledikten sonra hangi sinifin orneginin kullanacagina karar verecegimiz yer olan app.config dosyamizi yaratalim. Windsor’un kullanacagi section’u tanimlamamiz gerek.

<section name="castle" type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor"/>

Simdi ise Servicelere karar verecegimiz bolumu yazmamizda fayda var. Konsol uygulamasinda ConsoleLogger kullanmamiz gerektigini belirtelim.

  <castle>

    <component id="Logger"

       service="Logger.ILogger, Logger"

       type="Logger.Loggers.ConsoleLogger, Logger"/>

  </castle>

Service attribute’u genelde interface olarak tanimlanir. Windsordan ILogger talep ettigimizde bize ConsoleLogger instanceini gondermesi gerektigini soyluyoruz.

Simdi main applicationu acalim ve uygulamaya gecelim.

Gordugunuz gibi containerimiz initialize ettikten sonra, ILogger servisini cagirmak icin Resolve<T> methodunu kullaniyoruz. Bu method tum bagimliliklari cozmeye calisir, eger constructor injection kullaniyorsaniz cozumleyemedigi parametreleri varsa hata verir. Ne isime yaradi diyebilirsiniz, new’le neden yaratmadik? Dusunun ki Bir sinifiniz var ve bu sinif bir hata oldugunda loglama yapmak zorunda. Ve siz bu sinifi hem Windows uygulamada hem Web uygulamada kullanacaksiniz, Web uygulamada yapmasi gereken veritabanina kaydetmek olacakken Windowsta belli bir webservice’e hatayi iletme yolunu secebilirsiniz. Peki bu iki farkli uygulama icin ayni sinifi tekrar tekrar mi yazmayi tercih edersiniz yoksa xml config dosyasini degistirerek farkli yontemler kullanan ayni sinifi kullanmayi mi? Ikincisini kullanmak kodun yonetimi acisindan daha mantikli olacaktir.

Simdi gelin bir de Trace web uygulamamizi yazalim.

Web uygulamalarinda tercih edilen yontem IContainerAccessor arayuzunu implemente eden HttpApplication(a.k.a GlobalAsax) yaratmak. Boylece her istek yapildiginda ayni nesne ornegine erisebilirsiniz. Biz de buna uyalim ve HttpApplication sinifindan miras alalim.

Container property’sine erisince eger daha once yaratilmamissa yenisini yaratiyoruz. Diger bir deyisle singleton kalibini uyguluyoruz. Simdi de global.asax dosyamizi acip miras almasi gerektigi sinifi belirtiyoruz

      public class Global : TestWeb1Application

Ve deneme sayfamizi yazmaya baslayalim. Bunu yapmadan once bizim TraceLogger sinifimiz Trace kullandigi icin web.config icindeki system.web node’undan trace i acmamiz gerek

      <trace enabled="true" pageOutput="true"/>

 Simdi uygulamamiza bakalim

 

 

 

 

 

 

 

 

 

 

 

 

 

Dikkatinizi cekmek istedigim bir nokta var: Windsor’dan iki tane cozumleme istedigim halde bana bana gonderdigi nesnelerin ikisinin de HashCode()’lari ayni. Yani aslinda iki farkli nesnem yok elimde, windsor bana tek bir nesne gonderdi. Singleton ornek geldi aslinda elimize. Bu Windsorun varsayilan davranisidir. Farkli parametreler saglanmazsa daha once yarattigini gonderir. Buna mudahale etmek tabii ki mumkun. LifeCycle denilen yapilar bize bu imkani verir.

        <component id="Logger" service="Logger.ILogger, Logger" type="Logger.Loggers.TraceLogger, Logger" lifestyle="transient"/>

Mevcut LifeStylelarin listesini asagida veriyorum.

singleton

Farkli parametreler saglanmadikce nesnenin hep ayni ornegini dondurur

thread

Her thread icin farkli ornegi dondurur. Ayni thread icinden cagrilarda o thread icin daha onceden yaratilmis olan ornek varsa onu dondurur.

transient

Her cagrida farkli instance dondurur

pooled

Havuz yapisini kullanarak nesneleri dondurur.

custom

Castle bize kendi lifestyle’imizi implemente etme olanagini da veriyor.

 

Transient olarak belirttigimiz icin farkli sinif orneklerini aldik.

Simdi de makalemizin son parcasini hayata gecirelim

Configuration kisimlari bir onceki ornegimize benziyor.

Hatirlarsaniz bizim RepositoryLogger’imizin IRepository<T> bagimliligi vardi(constructoruna bakin goreceksiniz. Windsor bu bagimliligi cozumleyemedigi icin bize hata verdi.

Bu durumda bizim IRepository<T>’yi de windsor’a tanitmamiz lazim. Bunun icinse configuration’a eklememiz gereken

<component id="Repository" service="Logger.IRepository`1, Logger" type="Logger.SessionRepository`1, Logger" lifestyle="transient"/>

`1 ile gordugumuz yer interface’in-class’in generic oldugunu gosteriyor. Eger biz service’i

service="Logger.IRepository`1[[Logger.Loggers.Log]], Logger"

olarak tanimlayimis olsaydik, windsor generic repository’yi kullanmak yerine Log’lar icin ozellestirebilecegimiz, normal sessionrepository’den farkli olan baska bir sinifi kullanmasini saglayabilirdik fakat boyle bir tanimlama yapmadigimiz icin windsor bizim icin generic yapiyi secti.

 

Simdilik IoC, Dependency Injection ve Windsor icin soyleyebileceklerim bu kadar. Projeyi ekte sunuyorum. LogList.aspx sayfasinda bir baska uyarlama yaptim. IRepository<Log> ornegini cektim containerden. Sayfa icinde bu sekilde kullanmak, katmanlarin ayriligi prensibine her ne kadar ayri olsa da, anlatmasi kolay oldugu icin bu sekilde devam ediyorum. MVC patternde ve MVC frameworklerden Monorail’i anlatirken ne demek istedigimi daha rahat anlayacaksiniz.