Bir önceki yazılarda hatırlarsanız, AOP’in ne olduğunu, neden gerekli olabileceğini anlatmaya çalışmıştım. Şimdi de biraz kod üzerinden giderek AOP kavramlarını anlatmaya çalışacağım. AOP için birbiri ile kesişen ilgileri ayırmak için kullanabileceğimiz bir yaklaşım demiştik hatırlarsanız. Kod üzerinden giderek bu ilgileri nasıl ayırabileceğimizi 2 kısım şeklinde olacak bir yazı dizisi ile anlatmaya çalışacağım. Bu ilk kısımda birbiri ile kesişen ilgileri ayırmak için bir alt yapı oluşturacağız. Daha sonra ki yazımda ise yarattığımız nesnelere yeni ilgiler vererek bu alt yapı sayesinde ilgilerin aslında hiç kesişmeden basit bir biçimde uygulandığını göreceğiz. O zaman başlayalım…

Bu yazı için .NET Framework içerisinde bulunan ContextBoundObject sınıfından faydalanacağız. Bu noktada .NET Framework’de direk olarak bir AOP desteği olmadığı hatırlatmak isterim. Sadece .NET Framework’ün bize sunduğu sınıf ve metodlar ile bu yaklaşımı biraz olsun oluşturmaya ve daha iyi anlamaya çalışıyor olacağız.

Öncelikle ContextBoundObject sınıfının ne olduğu kısaca anlatmaya çalışacağım. Biraz sonra aşağıda vereceğim örneğin daha anlaşılır olması adına gerekli olacağına inanıyorum.

Bir içeriğe(context) ve bu içeriğin kurallarına bağlı olan nesnelere, içeriğe bağlı nesneler deniyor, yani context-bound objects. Peki içerik ne? Belli özelliklerin ve kullanım kurallarının kullanıldığı, nesnelerin bir araya geldiği oluşumlar diyebiliriz. Yani bir nesne bir içeriğe dahil olduğu ya da bağlandığı zaman o içeriğin özelliklerini ve kurallarını uygulamak durumunda oluyor. Bir nesne yaratılırken var olan bir içeriğe ya da meta-data’sında bulunan Attribute’lara(özelliklere) göre yaratılacak yeni bir içeriğe bağlanabilir. Burada ki özellik kavramı da ContextAttribute sınıfı ile .NET Framework içerisinde tanımlanmakta. Biraz daha geniş ve ayrıntılı bilgiyi buradaki MSDN içeriğinden öğrenebilirsiniz. Ama bizim için şimdilik bu kadarı yeterli olacak.

Yukarıda özetlemeye çalıştığım ContextBoundObject sınıfı ile oluşturacağımız nesnelerin bir içerik dahilinde oluşmasını sağlayacağız. Daha sonra bu içerik içerisinde ki kurallara göre kesişen ilgileri kontrollü bir şekilde yönetebileceğiz. Bunu nesneleri yaratırken, tam yaratmadan önce bazı kontrolleri yaparak, nesnenin bir metodunu çağırırken, tam çağırmadan önce bazı kontrolleri yaparak ya da nesnenin özelliklerini değiştirirken, tam değiştirme aşamasından önce yine bazı kontroller yaparak yapacağız. Burada ki kontroller de aslında birbiri ile kesişen ilgiler olacak. Biraz karışık oldu sanırım ama kod örnekleri ile daha net anlaşılacağına inanıyorum.

Öncelikle ContextBoundObject sınıfından türeyen bir Person sınıfı yaratalım…

 public class Person : ContextBoundObject
 {
        private int _age = 0;
        public Person():base()
        {

        }
        public int Age
        {
            get
            {
                return _age;
            }

            set
            {
                _age = value;
            }
        }

        public string DoSomething()
        {
            return String.Format("Person sınıfının DoSomething methodu çalıştı");
        }
 }

Fark etmiş olduğunuz gibi ContextBoundObject’ten türemesine rağmen pek bir farklılığı yok. Bu nesnenin yaratıldığı içeriğin özelliklerini(Attribute) belirlememiz lazım ki, yaratıldığı zaman o özelliğin sağlayabileceği yöntemler ve özellikler ile nesnenin üzerinde kontroller yapabilelim. Bunun için aşağıdaki gibi bir kod bloğu ile ContextAttribute’dan türeyen bir Attribute yaratmamız lazım.

 [AttributeUsage(AttributeTargets.Class)]//Bu özelliğin uygulanabileceği kullanımı belirtiyoruz
 public class ControllerAttribute : ContextAttribute
 {
        //ContextAttribute'un ismini base sınıf sayesinde belirtiyoruz.
        public ControllerAttribute()
            : base("Kontrolcu")
        {
        }

        //IsContextOK metodunu yeniden tanımlayarak, içeriğime bağlanan
        //nesnelerin özelliklerini kontroller edebiliyoruz. Eğer belirttiğimiz
        //özellikleri var ise bu içeriğimiz için uygun oluyor. Ama yok ise
        //özellikleri nesnemize bağlamak gerekiyor.
        //IsContextOK metodu, nesneler yaratıldığı zaman ilk çalışan metod.
        //Bu metodun sonucuna göre GetPropertiesForNewContext çalışıyor.
        public override bool IsContextOK(Context ctx, System.Runtime.Remoting.Activation.IConstructionCallMessage ctorMsg)
        {
            ControllerProperty property = ctx.GetProperty("Kontrolcu") as ControllerProperty;
            if (property == null)
                return false;
            return true;
        }

        //GetPropertiesForNewContext metodunu yeniden tanımlayarak,
        //içeriğimize özellikler ekliyoruz.
        //Bu özellikler içeriğe bağlanan nesnelerin uygulaması gereken kurallar
        //ve kontrolleri temsil ediyor.
        public override void GetPropertiesForNewContext(System.Runtime.Remoting.Activation.IConstructionCallMessage ctorMsg)
        {
            //Burada eklediğimiz ControllerProperty'si kendi yarattığımız,
            //IContextProperty arayüzünden yaratılan bir özellik.
            //Bunun gibi bir çok özellik yaratıp,içeriğimize ekleyebiliriz.
            ctorMsg.ContextProperties.Add(new ControllerProperty());
        }
}

Bu Attribute’u yaratırken dikkat ederseniz ControllerProperty şeklinde bir sınıfı özellik olarak ekledik. Bu sınıf, bizim nesnelerimizin içerik dahilinde uygulaması gereken bir özelliği belirtiyor. IContextProperty arayüzünden türeterek istediğimiz gibi yaratabiliriz. Bu arayüzden yarattığımız zaman aşağıdaki gibi tanımlamamız gereken metodlarımız olacak tabi ki…Ben kavram olarak kontrol eden şeklinde açıklamalarda bulunduğum için adını ControllerProperty olarak tanımladım.

public class ControllerProperty : IContextProperty
 {
        //Freeze metodu, özelliğin içeriği dondurabilmesini yönetiyor.
        //İçeriği dondurursak eğer yeni özellik eklemek ya da içerikten
        //özellik çıkarmak mümkün olmayacaktır.
        //Bu içeriklerin sınırlarını belirlemek adına kullanabileceğimiz
        //bir yaklaşım olabilir.Ama şimdiliu bu metod içerisinde
        //herhangi bir işlem yapmıyoruz.
        public void Freeze(Context newContext)
        {

        }

        //IsNewContextOK metodu bu özelliğin içerik için uygun olup olmadığını
        //kontrol eden bir metod. İçeriğe sadece kendi arayüzlerimizden oluşan
        //özellikler eklemek istediğimiz de örneğin, bu metod ile eklenen özelliğin
        // uygun olup olmadığını kontrol edebiliriz.Uygun ise true,değil ise false
        //şeklinde bir geri dönüş, bu özelliğin içeriğe eklenmesinde etki edecektir.
        public bool IsNewContextOK(Context newCtx)
        {
            return true;
        }

        //Özelliğimizin ismi.
        public string Name
        {
            get { return "Kontrolcu"; }
        }
 }

İçerik içerisinde ki özelliğimizi de yukarıda ki gibi tanımladıktan sonra bu özelliğin, içeriğe bağlanan nesneler tarafından uygulanabilir olmasına geldi sıra şimdi. Context’e(içerik) bağlanan nesneleri kullanırken, bu kullanımlardan önce araya girmemiz gerekmekte. Bunun için özelliklerimize mesaj alıcısı diyebileceğimiz alıcılar eklememiz lazım ki, içeriğe bağlanan nesneleri alabilen bir yapımız olsun.

Bunun için IContributeObjectSink arayüzünü ControllerProperty sınıfımıza eklememiz lazım. Bu arayüz .NET Framework içerisinde olan bir arayüz. Bu noktada bu sınıfın isminde geçen Sink kelimesinin lavabo olarak değil de alıcı olarak kullanıldığı hatırlatmak isterim. Çünkü birazdan benzer isimlere sahip olan sınıfları kullanıyor olacağız. (:

IContributeObjectSink arayüzünü sınıfımıza eklediğimiz zaman GetObjectSink() metodunu da tanımlamamız gerekiyor.

public class ControllerProperty : IContextProperty, IContributeObjectSink
{

        //Freeze metodu, özelliğin içeriği dondurabilmesini yönetiyor.
        //İçeriği dondurursak eğer yeni özellik eklemek ya da içerikten
        //özellik çıkarmak mümkün olmayacaktır.
        //Bu içeriklerin sınırlarını belirlemek adına kullanabileceğimiz
        //bir yaklaşım olabilir.Ama şimdiliu bu metod içerisinde
        //herhangi bir işlem yapmıyoruz.
        public void Freeze(Context newContext)
        {

        }

        //IsNewContextOK metodu bu özelliğin içerik için uygun olup olmadığını
        //kontrol eden bir metod. İçeriğe sadece kendi arayüzlerimizden oluşan
        //özellikler eklemek istediğimiz de örneğin, bu metod ile eklenen özelliğin
        // uygun olup olmadığını kontrol edebiliriz.Uygun ise true,değil ise false
        //şeklinde bir geri dönüş, bu özelliğin içeriğe eklenmesinde etki edecektir.
        public bool IsNewContextOK(Context newCtx)
        {
            return true;
        }

        //Özelliğimizin ismi.
        public string Name
        {
            get { return "Kontrolcu"; }
        }

        //IContributeObjectSink arayüzden gelen bu metod ile yeni bir alıcı yaratıyoruz.
        //Bu alıcı içeriğe bağlanan nesnelerin çağırımlarında çalışıyor olacak.
        //Burada Control isminde,IMessageSink(mesaj alıcısı) arayüzünden yaratılan
        //bir sınıfı metodun dönüş değeri olarak tanımlıyoruz.
        public IMessageSink GetObjectSink(MarshalByRefObject obj, IMessageSink nextSink)
        {
            return new Control(nextSink);
        }
}

Bu metod eklemesinde dikkatinizi çeken bir sınıf olacaktır. Control sınıfı bu örnek için yarattığım bir sınıf. IMessageSink arayüzünden yaratılan bir sınıf. Bu arayüz ile ilgili ayrıntılı bilgileri MSDN’den bakabilirsiniz. Bu arayüzün zorunlu kıldığı metodları tanımlamamız gerekmekte yine.

public class Control : IMessageSink
{

        private IMessageSink _messageSink;

        //İçeriğe dahil olan nesneler üzerinde kontroller yapabileceğimiz
        //nesneyi yaratmak için kullanacağımız yapıcı metodu,
        //alıcısını alacak şekilde tanımlıyoruz.
        public Control(IMessageSink sink)
        {
            _messageSink = sink;
        }

        //SyncProcessMessage metodu gelen alıcı mesaj aldığı zaman
        //çalışan bir metod. Bu metod ile alıcıya mesaj geldiği zaman
        //çeşitli kontrollerin yapılmasını sağlayabiliriz.
        //Burada mesaj diye tanımladığım operasyon aslında içeriğe dahil olan
        //nesnenin bir metodunun çalışması ya da bir özelliğinin çağrılması.
        //Bu örnekte şimdilik konsola bir mesaj yazdırıyor olacağız.
        public IMessage SyncProcessMessage(IMessage msg)
        {
            Console.WriteLine("Heyyy, önce bir dur bakalım...");
            return _messageSink.SyncProcessMessage(msg);
        }

        //AsyncProcessMessage metodu da SyncProcessMessage metodu ile aynı.
        //Sadece asenkron çağırımlarda çalışan versiyonu
        public IMessageCtrl AsyncProcessMessage(IMessage msg,
           IMessageSink replySink)
        {
            Console.WriteLine("Heyyy, önce bir dur bakalım...");
            return _messageSink.AsyncProcessMessage(msg, replySink);
        }

        //NextSink ile mesaj alıcımızı, içerik içerisinde ki diğer özelliklere
        //iletiyoruz. Bu sayede içerik içersinde bir sonraki özelliğe
        //geçiyoruz.
        public IMessageSink NextSink
        {
            get
            {
                return _messageSink;
            }
        }
}

Şimdi bütün bunları yaptıktan sonra ilk başta yaratmış olduğumuz Person sınıfına hangi içerik dahilinde olacağını belirtmemiz lazım. Bunun içinde ControllerAttribute’unu Person sınıfının hemen üstünde tanımlamamız gerekecek.

[Controller]
public class Person : ContextBoundObject
{
        private int _age = 0;
        public Person():base()
        {

        }
        public int Age
        {

            get
            {
                return _age;
            }

            set
            {
                _age = value;
            }
        }

        public string DoSomething()
        {
            return String.Format("Person sınıfının DoSomething methodu çalıştı");
        }
}

Bu tanımlamayı da yaptığımız zaman artık geriye bu Person sınıfımızı çağırabilecek bir basit test uygulaması yapmak kalıyor. Bunun içinde sanırım aşağıda ki gibi bir konsole uygulaması yeterli olacaktır.

class Program
    {
        static void Main(string[] args)
        {

            Person arda = new Person();
            Console.WriteLine("--Birazdan arda.Age özelliğine değer atanacak");
            arda.Age = 100;
            Console.WriteLine("--arda.Age özelliğine değer atandı");
            Console.WriteLine("--Birazdan arda.Age özelliği konsola yazılacak");
            Console.WriteLine(arda.Age);
            Console.WriteLine("--arda.Age özelliğini konsola yazıldı");
            Console.WriteLine("--Birazdan arda.DoSomething() metodu çalışacak");
            Console.WriteLine(arda.DoSomething());
            Console.WriteLine("--arda.DoSomething() metodu çalıştı");
            Console.ReadLine();

        }
    }

Bu programı çalıştırmadan önce isterseniz kısa bir özet geçelim ve neler yaptık bir bakalım.

  • ContextBoundObject sınıfından türeyen belli bir içeriğe bağlanacak bir sınıf yarattık.
  • Hangi içeriğe ekleneceğini ControllerAttribute şeklinde, ContextAttribute sınıfından türeyen bir sınıf yaratarak belirledik.
  • Bu ControllerAttribute ile içeriğimizin özelliklerini ekledik.
  • Özellik olarak ControllerProperty şeklinde, IContextProperty ve IContributeObjectSink arayüzlerinden yaratılan bir sınıf oluşturduk.
  • Bu özelliğin mesaj alması(nesneleri çağırma işlemi için) için IMessageSink şeklinde bir alıcı kullanmasını sağladık.
  • Control alıcısı ile nesnelerin çağırımları aşamasına müdahale etmek için gerekli metodların içini doldurduk.

Bu kısa özetten sonra isterseniz uygulamamızı çalıştıralım. Çalıştırdığımız zaman aşağıdaki gibi bir çıktımız olacaktır.

Uygulamanın kodları ile çıktımızı karşılaştırdığımızda, bir içeriğe bağlı olan nesnemizin çeşitli özelliklerine bazı operasyonlar yapmadan önce araya girmiş olduğumuza dikkat çekmek isterim. Aynı şekilde metod çalıştırmadan öncede araya girdiğimizi söyleyebilirim.

Bu noktada AOP kavramını daha iyi anlayabilmek adına bir sonraki yazıda oluşturacağımız ilgileri ve “Aspect”leri(görünüş) ayırabilmek adına bir alt yapı oluşturmuş olduk. Bir sonraki yazı da Person sınıfı üzerinde kullanacağımız ilgileri(örnek:loglama,hata yakalama) yaratarak bu yapı dahilinde nasıl kullanabileceğimizi anlatmaya çalışacağım.

Umarım açıklayıcı ve anlaşılır bir yazı olmuştur. Her türlü düşünce ve sorunuzu lütfen paylaşmaktan çekinmeyin. Bir sonraki yazıda görüşmek üzere…