Makale Özeti

Uygulamaların hem fonksiyonel hemde arayüz olarak genişletilebilir olması bir ihtiyaçtır. Fakat genelde uygulamalar tek parça halinde yazılırlar ve tak-çalıştır mantığına sahip bir genişletilebilme yeteneği sunmazlar. System.ComponentModel.Composition isim uzatında bu genişletilebilirlik problemi için isteyen her uygulamanın kullanabileceği basit bir genişletilebilir uygulama çözüm alt yapısı sunulmaktadır.

Makale

Genişletilebilir Uygulama Yazı Dizisi

  • Genişletilebilir Uygulama Yazma
  • Derinlemesine Genişletilebilir Uygulama Yazma
  • Genişletilebilir Uygulamada Hata Yakalama ve İzleme
  • Uygulamaların hem fonksiyonel hem de ara yüz olarak genişletilebilir olması bir ihtiyaçtır. Fakat genelde uygulamalar tek parça halinde yazılırlar ve tak-çalıştır mantığına sahip bir genişletilebilme yeteneği sunmazlar. System.ComponentModel.Composition isim uzayında bu genişletilebilirlik problemi için isteyen her uygulamanın kullanabileceği basit bir genişletilebilir uygulama çözüm alt yapısı sunulmaktadır. Alt yapı çalışma anında uygun genişleletme parçalarını bulur ve yükler. Ayrıca alt yapı yüklenen parçaların yaşam döngülerini de takip eder.

    System.ComponentModel.Composition ile birlikte uygulamalar için Katalog (Catalog) ve taşıyıcı (Container) kavramları gelmektedir.

    Katalog sadece uygulama genelinde ki dağıtım(Export) tanımlarını saklamaktadır. Yani uygulamanın ihtiyaç duyduğu bir nesnenin nereden ve nasıl oluşturulacağının cevabını katalog vermektedir. Katalog çalışma zamanında kendisini güncelleyen bir yapıdadır. Belirtilen dizinde ki dll’ler içinden dağıtım tanımlarını toplayabilmektedir. Dizin içinde ki dll’ler değiştikçe katalogda kendisini güncellemektedir.

    Taşıyıcı ise parçaları(Part) bir araya getiren kısımdır. Her bir parça kodlanırken uygulama geneline ithal etmek istediği (Export) tanımları ve uygulamadan almak istediği (Import) ihraç tanımları söylemektedir. Taşıyıcı çalışma anında parçaları oluşturur ve kullanımda olduğu sürece saklar.

    Taşıyıcı ve katalog arasında çalışan fakat bizim görmediğimiz ExportProvider sınıfı vardır. ExportProvider sınıfı parçaları oluşturan sınıftır. Taşıyıcı ve katalog ile konuşarak kendisinden istenen parçayı üretmektedir.

    Ortamı Hazırlama

    Katalog Oluşturma

    Uygulama ilk olarak tip tanımlarını saklayacağı bir tip katalog oluşturmalıdır. En sık kullanılan kataloglardan ilk AssemblyCatalog’dur. AssemblyCatalog verdiğiniz assembly dosyasını taramakta ve parça kontratlarını saklamaktadır.

     var  assemblyCatalog = new  AssemblyCatalog (Assembly.GetExecutingAssembly ());

    Başka bir katalog türü ise DirectoryCatalog’dur. DirectoryCatalog ise verilen dizinde ki tüm assembly dosyalarını taramakta ve bulduğu tüm parça taşıma kontratlarını saklamaktadır.

     var  directoryCatalog = new  DirectoryCatalog (Path .Combine (".." , "Plugin" ));

    Birden çok katalogu AggregateCatalog ile bir arada kullanarak daha geniş bir parça taşıma kontratı havuzuna sahip olabilirsiniz.

     var  aggregateCatalog = new  AggregateCatalog (assemblyCatalog, directoryCatalog);

    Bu üç katalog dışında daha az kullanılan SilverLight’a özel DeploymentCatalog ve sadece parametre olarak verilen nesneler için tip katalogu oluşturan TypeCatalog sınıfları da mevcuttur.

    Taşıyıcı Oluşturmak

    Parçalarımızın program içinde nasıl hareket edeceğini gösteren kontratlarımızı barındıran katalogumuzu oluşturduk. Şimdi sıra katalogu kullanacak olan taşıyıcıyı oluşturmaktadır. Taşıyıcıyı iki şekilde oluşturabilirsiniz. Eğer taşıyıcıya hiçbir katalog vermeden oluşturursanız taşıyıcı varsayılan olarak katalogsuz olarak oluşturacaktır. Fakat katalog olmadığı için parçaları oluştururken gerekli tüm parçaları doğrudan taşıyıcıya eklemeniz gerekecektir. Burada dikkat edilmesi gereken husus taşıyıcı [Export] [Import] niteliklerini sahip parçalar ile çalışabilmektedir.

     var  compositionContainer = new  CompositionContainer ();

    Katalogsuz taşıyıcı kullanma durumu hemen hemen hiç kullanılmamaktadır. Genelde katalogsuz olarak taşıyıcı oluşturulmamaktadır. Eğer kendinize ait bir katalog oluşturdunuzsa taşıyıcının bu katalog ile çalışmasını isteyebilirsiniz.

     var  aggregateCatalog = new  AggregateCatalog (assemblyCatalog, directoryCatalog);
     var  compositionContainer = new  CompositionContainer (aggregateCatalog);

    Parçalar ve Kontratlar

    Katalog ve taşıyıcıyı oluşturduktan sonra sıra uygulama parçalarını oluşturmaya geldi. [Export] ve [Import] niteliğine sahip tüm sınıflar, ara yüzler, değişkenler, metotlar program parçası sayılmaktadır. [Export] ve [Import] nitelikleri ile parçalar taşıyıcıya uygulama içinde nasıl hareket edeceğini yani taşınma kontratlarını belirtmektedir.

    Dışa Aktarım

    Her hangi bir parçaya sadece [Export] niteliği ekleyerek dışa aktarılabilir olmasını sağlayabilirsiniz.

     // 1- Basit dışa aktarım
     [Export ]
     public  class  SimpleContractExport

    Sınıfınızı üst sınıfın bir örneği olarak da dışa aktarabilirsiniz.

     // 2- üst sınıfı ile dışa aktarım kontratı 
     [Export (typeof (SimpleContractExport ))]
     public  class  SimpleDrivenTypeExport  : SimpleContractExport

    Sınıfınızı özel bir etiket belirterek de dışarı aktarabilirsiniz. Bu durumda aynı etiket ile çağrıldığında bu dışa aktarım kontratı çalışacaktır.

     // 3- etiket ile dışa aktır kontratı
     [Export ("SimpleTagedTest" )]
     public  class  SimpleTagedTypeExport

    Dışarı aktarmak istediğiniz bir ara yüzün uygulaması da olabilmektedir.

     // 4- bir arayüz uygulması olarak dışa aktarma
     [Export (typeof (ISimpleInterface ))]
     public  class  InterfaceContractExport  : ISimpleInterface

    Her zaman bir sınıfı aktarmak zorunda değilsiniz. Diğer program bileşenleri de dışarı aktarılabilmektedir. Her hangi bir property dışarı aktarılabilmektedir.

     // 5- bir property olarak dışa aktarım kontratı
     [Export ("NextString" )]
     public  string  NextStringProperty

    Yukarıdaki örnekte bir etiket verilmiştir çünkü bir string tipi dışarı aktarılmaktadır. Birçok string tipi arasında bulabilmek için bir etiket verilmiştir. Genelde property olarak aktarımlar Factory Pattern uygulamak için kullanılmaktadır.

     // 5- bir property olarak dışa aktarım kontratı
     [Export ]
     public  ISimpleInterface  SimpleInterfaceFactory 
     {
         get 
         {
             return  new  OtherInterfaceContractExport ();
         }
     }

    Tam bu noktada nesnelere police enjekte etmek için ara bir çözüm bulunmaktadır. Eğer uygulamanızda ki sadece birkaç nesne üzerinde “Policy Injection” kullanıcaksanız property olarak aktarım işinize yarayacaktır.

     // 5- bir property olarak dışarı aktarım  
     // ve özel bir sınıfta policy injection kullanımı
     [Export ]
     public  ISimpleInterface  SimpleInterfaceFactoryWithPI 
     {
         get 
         {
             return  RoC.PI.ProxyCreator<OtherInterfaceContractExport >
                 .CreateObject ();
         }
     }

    Sadece property’leri değil event’larıda dışarı aktarabilmektesiniz.

     // 6- bir metod olarak dışa aktarım kontratı
     [Export (typeof (Func<string >))]
     public  string  GetNext ()

    Olayları uygulama genelinde paylaşırken dikkatli olmak gerekmektedir. Zira bir olayı dışarı aktarmak için taşıyıcı önce olayı barındıran sınıfı oluşturacaktır. Olayı barındıran sınıf olaya bağlanan tüm delegeler yok olduğunda yok edilecektir. Fakat olaya bağlanan delegelerin olduğu sınıflarda yok edilmek içinde delegelerin yok edilmesini bekleyecektir. Yani uygulamanızda gereksiz yere bir birini bekleyen nesneleriniz kalacak ve bunlar yok edilmeyecektir. Bu durumda kullanılacak WeakReference , WeakEventPattern ve EventAggregator gibi yöntemler bulunmaktadır. Başka bir yazıda açıklamak üzere uygulama genelinde olay paylaşımının tehlikesinden bahsedip geçmiş olalım.

    Bazen de bir üst sınıf veya ara yüze ait tüm alt sınıfların dışarı aktarılabilir olmasını istersiniz. Bu durumda üst sınıfınızı işaretleyerek tüm alt sınıflarınızın taşınabilir olmasını sağlayabilirsiniz.

     // 7- bir üst sınıf olarak dışa aktarım kontratı
     [InheritedExport ]
     public  interface  INext { .. }
     public  class  NextClass1  : INext { .. }
     public  class  NextClass2  : INext{ .. }

    Böylelikle kullanabileceğimiz dışa aktarım kontratlarını inceledik. Hafızada yer tutan tüm programlama öğelerini dışarı aktarabiliriz. Aktarırken bir etiket verebilir veya program öğesinin tipi ile değil de dönüşebileceği her hangi bir tip ile aktarabiliriz.

    İçe Aktarım

    Tabii ki dışarı aktardığımız program parçalarının diğer program parçaları tarafından içeri aktarılması gerekmektedir. Dışa aktarıma benzer şekilde içe aktarım yolları vardır.

    Nesnenin oluşması sırasında Cunstructor parametresi olarak diğer parçaları ithal edebilirsiniz. Aşağıdaki örnekte SimpleImport nesnesi oluşturulurken taşıyıcı InterfaceContractExport tipine ait bir dışa aktarım kontratı olup olmadığına bakar. Bulduğu InterfaceContractExport kontratı ile eğer taşıyıcı üzerinde nesne örneği yoksa önce aktaracağı InterfaceContractExport nesnesini oluşturur. Taşıyıcıda içe aktarılacak InterfaceContractExport nesne örneğini alır ve SimpleImport sınıfına constructor parametresi olarak gönderir.

     // 1- constractor parametresi olarak kontratları içe aktarım
    [ImportingConstructor ]
    public  SimpleImport (InterfaceContractExport  simpleInterfaceImplemantation)

    Her zaman ithal etmek istediğiniz parça için bir dışa aktarım kontratı olmayabilir. Eğer ithal etmek istediğiniz parça var ise parçayı ithal etmeniz gerekmektedir.

     // 2- opsiyonel içe aktarı 
     // eger import edilen parça var ise getir yoksa Default(T) değerini getir 
     [ImportingConstructor ]
     public  SimpleImport ([Import (AllowDefault  = true )]INext  next)

    İçe aktarımı property olarak da kullanabilirsiniz. Nesne create edildikten sonra property aktarımları atanacaktır.

     // 3-  Property olarak kontratları içe aktarım
     [Import ]
     public  SimpleContractExport  Simple  { get ; set ; }

    Property içe aktarımla aynı şekilde değişkenlerde de içe aktarım yapılabilmektedir.

     // 4. Field olarak içe aktarım 
      // etiketleri tüm aktarımda kullanabilirsiniz
     [Import ("SimpleTagedTest" )]
     private  SimpleTagedTypeExport  _simpleTagedTypeExport ;

    Eğer içe aktarmak istediğiniz parça taşıyıcı üzerinde birden çok varsa bunların hepsini birden alabilirsiniz. Bir sonra ki yazıda taşıyıcı üzerinde ki parçalar üzerinde sorgular çalıştırıp istediğiniz özel parçaları bulmayı inceleyeceğiz. Şimdilik belirli bir tipte ki tüm parçaları alalım.

     // 5 -kontratları çoklu içe aktarım
     [ImportMany ]
     private  INext [] _nextArray ;

    Bu şekilde parça paylaşan programlarda en büyük ihtiyaçlardan biriside her hangi bir parçanın ilk erişildiğinde oluşturulmasıdır. Yani içe aktardığınız parçaya ait bir metot veya property erişimi yapana kadar parça oluşturulmayacaktır. Parçayı kullanan sınıf ilk ne zaman erişim sağlarsa o zaman içe aktarılan parça oluşturulacaktır.

     // 6 -kontralları gevşek bağlama (lazyload ) ile içe aktarım
     // ilk erişeme kadar nesneyi taşıyıcı üzerinden almaz.
     // ilk erişimde nesne taşıyıcıdan getirilir
     [Import ]
     public  Lazy <ISimpleInterface > SimpleLazy  { get ; set ; }
     public  void  UseLazy ()
     {
         // SimpleLazy.IsValueCreated  == false
     
         // ilk erişimi yapılıyor bu kod satırı çalışana
         // ISimpleInterface nesne örneği oluşturulmuyor
         SimpleLazy .Value .GetExecutingAssemblyName ();
     
         // SimpleLazy.IsValueCreated  == true 
     }

    Basit Genişletilebilir Uygulama

    Basit bir uygulama ile genişletilebilir (composite) özelliklerini test edelim. Öncelikle katalogu olmayan bir taşıyıcıyı nasıl kullanacağımızı görelim:

     [Export ]
     public  class  ParameterTest1 
     {
         public  int  Number1 { get  { return  10; } }
     }
     public  class  ParameterTest2 
     {
         [Export ("ParameterTestNumber" )]
         public  int  Number2  { get  { return  11; } }
     }
     public  class  ContainerWithOutCatalog 
     {
         public  ContainerWithOutCatalog ()
         {
             // katalog olmadan taşıyıcı oluştur
             var  container = new  CompositionContainer ();
             // this içinde ki [Import] ları parametrelerle doldur
             container.ComposeParts (this ,new  ParameterTest1 (), new  ParameterTest2 ());
             Console .WriteLine (
                 "ContainerWithOutCatalog.ParameterTest.Number1:{0}" ,
                 ParameterTest .Number1 );
             Console .WriteLine (
                 "ContainerWithOutCatalog._numberTest:{0}" ,
                 _numberTest );
         }
         [Import ]
         public  ParameterTest1  ParameterTest  { get ; set ; }
         [Import ("ParameterTestNumber" )] private  int  _numberTest ;
     }

    Yukarıda ki kodlar sorunsuz şekilde çalışmaktadır. Dikkat edilmesi gereken [Import] yapılan tüm parçaların ComposeParts fonsiyonuna parametre olarak vermek durumundasınız. Çünkü gerekli parçalara ait [Export] kontratlarını alabilecek bir kataloga sahip degilsiniz.

    Kataloga sahip bir taşıyıcı ile çalışmayı görelim:

     public  class  ContainerWithCatalog 
     {
     public  ContainerWithCatalog ()
     {
         var  assemblyCatalog = new  AssemblyCatalog (GetType ().Assembly );
         var  compositionContainer = new  CompositionContainer (assemblyCatalog);
         compositionContainer.ComposeParts (this );
         //üüm [Export] ve [Import]ıı y dene 
         Console .WriteLine ("ContainerWithCatalog.ImportSimple:{0}" , 
             _simpleImport .SimpleContractImport .Number ());
         Console .WriteLine (
             "ContainerWithCatalog.ImportAllowDefault:{0}" ,
             _simpleImport .NonExported  == default (INext ) ? "default"  : "non default" );
         Console .WriteLine ("ContainerWithCatalog.ImportDrivenType:{0}" ,
             _simpleImport .DrivenTypeImport .Number ());
         Console .WriteLine (
             "ContainerWithCatalog.ImportWithLabel:{0}" ,
             _simpleImport .GetSimpleTagedTypeExport2 ());
         Console .WriteLine (
             "ContainerWithCatalog.ImportingConstructor:{0}" ,
             _simpleImport .SimpleInterface .Number ());
         Console .WriteLine (
             "ContainerWithCatalog.ImportProperty:{0}" ,
             _simpleImport .NextStringProperty );
         Console .WriteLine (
             "ContainerWithCatalog.ImportEvent:{0}" ,
             _simpleImport .NextNumber .Invoke ());
         Console .WriteLine (
             "ContainerWithCatalog.ImportMany:{0}" ,
             _simpleImport .WriteAllNext ());
     }
     [Import ] private  SimpleImport  _simpleImport ;
     }

    Uygulamanın çıktısı şu şekilde görülecektir:

    Temel seviyede genişletilebilir bir uygulama geliştirebilir duruma geldik. Artık özgürüz! Kendimiz için bir katalog oluşturabiliriz. Oluştuğumuz katalogu kullanarak uygulama parçalarını bir araya getirecek taşıyıcıyı oluşturabiliriz. Taşıyıcı uygulama parçalarımızı bizim belirlediğimiz kontratlar ile uygulama içinde yönetecektir. Bir sonra ki yazıda katalog üzerindeki kontratları nasıl sorgulayacağımızı, parçalarımızın hayat döngüsünü, taşıyıcıyı ve katalogu çalışma anında değiştirmeyi inceleceğiz.

    Emre Coşkun

    http://www.emrecoskun.net