29 Temmuz 2007 Pazar

Injection Pattern

Uygulamanız içinde nesneler arasında ilişkileri nasıl kontrol edersiniz? Mesela uygulamanızda oluşturulan her DbConnection nesnesini bir havuz üzerinde toplamanız gerekti ve bu ihtiyaç projenin ortasında oluştu ne yaparsınız? Geriye dönüp tüm kodları yenileyecek misiniz? Ya da bir sınıfın her örneğinin config dosyanızda yazılan parametrelere göre oluşturulmasını istiyorsunuz ne yaparsınız?

Aşağıda örnek bir Stok projesinin parçaları ve bu parçaların bir birleri ile olan ilişkisi mevcuttur.

Şimdi klasik bir yöntemle bu projeyi kodlayalım. Muhtemel kodlamamız şu şekilde olacaktır.

[cs]

public class App {
    public App() {
        quotes = new StockQuotes();
        authenticator = new Authenticator();
        database = new Database();
        logger = new Logger();
        errorHandler = new ErrorHandler();
    }
}


Uygulama sınıfımız için alt parçalara ulaşmak gayet acık ya alt parçalar birbirleri ile nasıl haberleşir. Yani StockQuotes nasıl Logger elemanını bulur? Veya Database kullanıcı yetkilendirmesini nasıl yapar?

Muhtemel kodlamanızda her bir parça kendi içinde ihtiyaç duyduğu diğer sınıfları yeniden oluşturacaktır.

[cs]

public class StockQuotes {
    public StockQuotes() {
        logger = new Logger();
        errorHandler = new ErrorHandler();
    }
    internal Logger logger;
    internal ErrorHandler errorHandler;
}


Burada bir sorun görüyor musunuz? Aynı sınıfa ait nesneler mükerrer defa oluşturulmakta. Bu durumu iyileştirmek için tüm nesneleri içinde saklayacak yeni bir sınıf yazalım. Tüm gerekli nesneleri bu sınıf içinde bir defa oluşturalım ve daha sonra aynı sınıf üzerinden erişelimi

[cs]

public interface ILocator {
    TObject Get();
    object Get(Type objectType);
}
 
public class MyLocator :ILocator {
    protected Dictionary dict;
 
    public MyLocator() {
        dict = new Dictionary();
 
        dict.Add(typeof(ILogger), new Logger());
        dict.Add(typeof(IErrorHandler), new ErrorHandler(this));
        dict.Add(typeof(IStockQuotes), new StockQuotes(this));
        dict.Add(typeof(IDatabase), new Database(this));
        dict.Add(typeof(IAuthenticator), new Authenticator(this));
        dict.Add(typeof(App), new App(this));
    }
}


Tüm sınıfları saklayan anahtar olarakta sakladığı sınıfın arayüzünü kullanan bir üst sınıf ile mükerrer defa nesne oluşturma problemini aşabiliriz. Uygulama kodlarımız şu şekilde olacaktır:

[cs]

public App(ILocator locator) {
    quotes = locator.Get();
    authenticator = locator.Get();
    database = locator.Get();
    logger = locator.Get();
    errorHandler = locator.Get();
}
 
public class StockQuotes :IStockQuotes{
    public StockQuotes(ILocator locator) {
        logger = locator.Get();
        errorHandler = locator.Get();
    }…


Bu çözümün güzel tarafı sınıflar ihtiyaç duydukları sınıfları kolaylıkla alabiliyorlar.

Ama bizim için tam bir çözüm değil. Her obje ILocator ile ilişkili, ayrıca nesne oluşturma sırası hala problemli. Uygulama sırasında anlık olarak kullanılmayan nesneler hala saklanmaktadır. O halde birde LifeTime Continer nesnesine ihtiyacımız var. Locator nesneleri bu LifeTimeContainer üzerinde saklamalı LifeTimeContainer referansı kalmayan nesneleri kendisi temizlemelidir.

LifeTime Continer nesnesini kullanabilmemiz için tüm oluşan objelerden haberdar olmamız gerekmektedir. Madem her nesne oluşturulurken çalışacak bir sınıf yazacağız bu sınıfı Stragy Pattern destekler şekilde yazalım daha sonradan eklenecek yeni Stragy sınıfları ile entegre edebilelim. Tabi çok fazla sayıda Stragy ve Policy sınıfı olunca bunların bir sorumluluk zinciri şeklinde çalışmaları gerekmektedir. P&P grubunun hazırladığı Builder sınıfı bizim için bu işlemi yapmaktadır.

Nesnelerin Creation anını kontrol edebildiğimize göre artık nesnelere ihtiyaç duydukları tüm nesneleri ‘enjekte’ edebiliriz. Önce Builder sınıfını uygulayacak bizim örnek uygulamamızda tüm nesneleri yönetecek örnek bir DependencyContainer sınıfı yazalım ve daha sonra nesnelere enjekte olayının nasıl uygulandığına bakalım.

[cs]

public DependencyContainer() {
    _builder = CreateBuilder(null);
    _locator = new Locator();
    _container = new LifetimeContainer();
    _locator.Add(typeof(ILifetimeContainer), _container);
}
 
private IBuilder CreateBuilder(IBuilderConfigurator  configurator) 
{
 IBuilder result = new BuilderBase();
 result.Strategies.AddNew(BuilderStage.PreCreation);
 result.Strategies.AddNew(BuilderStage.PreCreation);
 result.Strategies.AddNew(BuilderStage.PreCreation);
 result.Strategies.AddNew(BuilderStage.PreCreation);
 result.Strategies.AddNew(BuilderStage.PreCreation);
 result.Strategies.AddNew(BuilderStage.Creation);
 result.Strategies.AddNew(BuilderStage.Initialization);
 result.Strategies.AddNew(BuilderStage.Initialization);
 result.Strategies.AddNew(BuilderStage.PostInitialization);
 result.Policies.SetDefault(new DefaultCreationPolicy());
 if(configurator != null)
      configurator.ApplyConfiguration(result);
 return result;
}


Kendi özel Strategy sınıflarımızı yazıp burada DependencyContainer üzerine ekleyebiliriz. Bu tüm nesne Creation işlemlerinde çalışacak bir kod bloğu manasına gelir. Uygulamaya sonradan kazandırılacak yetenekler için harika bir durumdur.

DependencyContainer sınıfımıza diğer nesnelere hizmet verecek şekilde biraz daha genişletelim.

[cs]

public TBuild Get() {
    return _builder.BuildUp(_locator, null, null);
}
 
public void RegisterSingleton() {
    _builder.Policies.Set(
 new SingletonPolicy(true), typeof(TBuild), null);
}
 
public void RegisterTypeMapping() {
    _builder.Policies.Set(
 new TypeMappingPolicy(typeof(TToBuild), null), typeof(TRequested), null);
}


Artık nesneler DependencyContainer içinde register olan her hangibir sınıf örneğini kolayca Get<T>() ile alabilirler.Nesneler oluşurken kendi ihtiyaçlarını Builder sınıfı sağlayacaktır.

[cs]

DependencyContainer container = new DependencyContainer();
IStockQuotes stock = container.Get();
 
public class StockQuotes :IStockQuotes {
    public StockQuotes(IErrorHandler handler, Logger logger) {
        _errorHandler = handler;
        _logger = logger;
    }
 
    Logger _logger;        
    public Logger Logger {
        get {
            return _logger;
        }
    }
 
    IErrorHandler _errorHandler;
    public IErrorHandler ErrorHandler {
        get {
            return _errorHandler;
        }
    }
}


Sadece constructor içinde değil property veya fonksiyonlara da enjekte yapabiliriz.

[cs]

public class Database :IDatabase {
    public Database() {            
    }
    Logger _logger;
    [Dependency]
    public Logger logger {
        set {
            _logger = value;
        }
        get {
            _logger;
        }
    }
    [InjectionMethod]
    public void Init(IErrorHandler handler) {
        
    } 
}

DependencyContainer veya CAB içinde ki adı ile Builder sınıfı konfigürasyon ile nesne oluşturabilir. Bunun için hazırlanmış IBuilderConfigurator ara yüzü vardır. Kullanımı insan sihirli kod bloğu hissini verir. Nesneleriniz sizin config dosyası üzerinde yazdığınız verilere göre oluşmaktadır. Tüm property metot parametreleri her şeyi config üzerinden halledebilirsiniz.

<ContainerConfig xmlns='container-config'>
    <Mappings>
        <Mapping FromType='Object1' ToType='Object1'/>
    </Mappings>
    <BuildRules>
        <BuildRule Type='Object1' Mode='Instance'>
            <Constructor>
                <Value Type='System.String'>ABC</Value>
            </Constructor>
            <Method Name='SetLength'>
                <Value Type='System.Int32'>123</Value>
            </Method>
        </BuildRule>
    </BuildRules>
</ContainerConfig>

[cs]

public class Object1 {
    public Object1(string color) {
        _color = color;
    }
    public void SetLength(int length) {
        _length = length;
    }

Şimdi burada durup birde büyük resme bakalım. ObjectBuilder kütüphanesi tüm P&P grubu ürünlerinde başrol oynamaktadır. Biz farkında olmasak bile mesela bir xml üzerinden config verilerini okurken bile bu kütüphane iş başındadır. CAB içerisinde her WorkItem nesnesi kendi Builder nesnesine sahiptir. Her WorkItem RootWorkItem ile entegre çalıştığı için asıl LifeTime Container RootWorkItem üzerindedir.

Böylelikle en zor konu olan Injection Pattern sonuna geldik. Eğer bu konuyu anladı iseniz P&P grubuna ait tüm ürünleri rahatlıkla kullanabilirsiniz. P&P grubu bu pattern ile nasıl harika işler çıkartmış incelemeye devam edeceğiz. Hafta gerçek programlar yazmaya SCSF ile uygulama geliştirmeye başlıyoruz.