Hatırlayacak olursanız son bir kaç yazıdır AOP hakkında bir şeyler paylaşıyordum. İlk iki yazı teorik, son yazı ise biraz daha uygulamaya yönelik olmuştu. Bu yazıda ise, bir önceki yazıda başlamış olduğum örneği biraz daha geliştirip, AOP’ı daha iyi anlamaya çalışacağız. Ama isterseniz önceki yazıları bir hatırlayalım…

  1. ‘Aspect Oriented’ programlama mı…Nedir ki?
  2. Peki ama neden ‘Aspect Oriented’ programlama…
  3. ‘Aspect Oriented’ programlamaya başlıyoruz…Bölüm I

Bu yazıda bir önceki kod alt yapısını üzerinden giderek, AOP kavramında ki Aspect’leri yaratıyor olacağız. Özellikle “Aspect” şeklinde belirtmek istiyorum ki, yarattığımız bazı örnek sınıfları bu şekilde düşünmeye çalışmak, konunun bütününü anlamak adına yardımcı olacaktır. Hatırlarsanız bir önceki yazıda Person diye bir sınıf oluşturmuştuk örnek olarak ve bu sınıfı bir tane konsol uygulamasında kullanarak methodlarını çağırmadan önce nasıl araya girebileceğimize değinmiştim. Yine aynı sınıf üzerinden, onu biraz daha geliştirerek gidiyor olacağız. Bunun için sınıfımızı aşağıdaki gibi yenilememiz yeterli olacaktır şimdilik. Yeni bir özellik ve metod dışında aslında fazla bir şey de yok.

    [Controller]
    public class Person : ContextBoundObject
    {
        private int _age = 0;
        private string _name = "";

        public string Name { get { return _name; } }

        public Person(string name) : base() { _name = name; }

        public int Age
        {

            get
            {
                return _age;
            }
            set
            {
                _age = value;
            }
        }

        public string DoSomething()
        {

            string logMessage = "Person sınıfının DoSomething() methodu çalıştı";
            Console.WriteLine(logMessage);

            return "";
        }
        public void DoAnotherThing(int parameter1, string parameter2)
        {
            Console.WriteLine("Person sınıfının DoAnotherThing() methodu çalıştı");
        }

    }

AOP, yazılımda ki kesişen ilgileri ayırmak için kullanabileceğimiz bir yöntemdi demiştik. Bu ilgiler her yazılım ürününde bir şekilde olan Logging,Exception Handling gibi kavramlar olabileceği gibi, iş kuralları ve bu kuralların işletildiği methodlar da olabilir. Ama genellikle bir biri ile çakışan ilgiler Logging,Exception Handling, Security gibi kavramlar için daha karmaşık olur. Oluşan Exception için uygun kaydı tutmak, ya da bir metod çalıştığında bu metodun çalışmasına dair bazı bilgileri kayıt altına alabilmek bir çok kavramın iç içe girmesine sebep olur. Bu yazıda bu ilgilere biraz daha yer vererek, bu ilgileri AOP yaklaşımı ile nasıl ayırabileceğimizi anlamaya çalışacağız. Örnek olabilecek bir kaç ilgiyi oluşturup, bunu Person sınıfımızda kullanacağız. Bu ilgileri .NET Framework’ünde bulunan Attribute‘lar ile yaratıp, nesnelerimize,metodlarımıza ve nesnelerin özelliklerine bağlayacağız. Aşağıdaki gibi bir kaç nesne, örnek olması adına yeterli olacaktır diye düşünüyorum.

    //Aspect'lerimiz için ortak bir yapı oluşturuyoruz.
    //Bu örnek için oldukça basit bir yapı oluşturmak
    //şimdilik yeterli olacaktır.
    //Attribute sınıfından türeyen bir BaseAspect sınıfı
    //oluşturuyoruz.
    public abstract class BaseAspect : Attribute
    {
        //abstract anahtar kelimesi ile tanımladığımız bu metodu
        //BaseAspect sınıfından türeyen tüm sınıflar yaratmak durumunda.
        //Bu method bu Aspect'in yapmakla yükümlü olduğu metodu simule ediyor
        //olacak...
        public abstract void Process(ref IMethodCallMessage message);
    }

    //Uygulamamızda Log'lama gerektiren yerlerde kullanabileceğimiz bir
    //ilgiyi bu şekilde tanımlayabiliriz.Bu Log'lama için ne yapılması gerekiyorsa
    //kendi içinde yapıyor olacak. BaseAspect sınıfımızdan gelen Process methodunun
    // message parametresinden istediğimiz özellikleri ihtiyacımıza göre alabiliriz.
    //Eğer kendi Log'lama için kullandığımız arayüzler varsa onları message parametresinden
    //çekip kullanmak mümkün.Bu örnekte konsola kayıt mesajı yazdıracağız.
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Method)]
    public class Logger : BaseAspect
    {
        public Logger()
        {

        }

        public override void Process(ref IMethodCallMessage message)
        {
            StringBuilder sb = new StringBuilder();
            if (message.Args != null)
            {
                //Metoddan gelen parametreleri yazdırıyoruz.
                for (int i = 0; i < message.Args.Length; i++)
                {
                    sb.Append(String.Format("{0}={1}\r\n\t\t\t\t",message.GetArgName(i),message.Args[i]));
                }
            }

            Console.WriteLine("##LOG## "+DateTime.Now.ToString()+" - "+ message.MethodName + " methodu çalışacak.\r\n\t\t\t\t" + sb.ToString() );
        }
    }

    //CanDrive adındaki Aspect'imiz aslında bir iş kuralını temsil ediyor
    //Aldığı parametre ile bir yaş sınırı belirlediğimiz bu ilgimiz
    //beraber kullanıldığı parametrenin yaş özelliğini kontrol ediyor.
    //Bu örnekte önemli olan nokta message parametresinden bize lazım olan bir
    //nesneyi kullanabilmemiz. Bu noktada altını çizmek isteyeceğim bir nokta var.
    //CanDrive kuralı kavram olarak hangi nesneler ile ilgili olacaksa bunların çok iyi belirlenmesi
    //gerekmekte. Buna göre içerisinde ki iş kuralı ve metodları dikkatlice yazılmalı.
    //Bu örnekte CanDrive, Person sınıfını kullanıyor. Çünkü CanDrive'ın ilgisi Person ile...
    //İlk yazımda da dediğim gibi AOP'i ilgileri ayırmak ve ayrıca yönetmek içinde kullanabiliriz.
    [AttributeUsage( AttributeTargets.Parameter)]//Sadece parametrelerde kullanabiliriz bu ilgiyi...
    public class CanDrive : BaseAspect
    {
        private int _age;

        public CanDrive(int age)
        {
            _age = age;
        }
        public override void Process(ref IMethodCallMessage message)
        {
            Person p = (Person)message.GetArg(0);
            if (p.Age < _age)                 
               throw new Exception(String.Format("{0} araba süremez.Yaşı küçük.{1}", p.Name, p.Age));         
            
	}  
    }  

//Bu seferde belli yaş aralığını kontrol eden ilgimiz var.     //CanDrive'a kavram olarak benziyor. Ama uygulama konusunda farklılık var.     
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)]     
public class AgeValidation : BaseAspect     
{         
      private int _age;         
      public AgeValidation(int age)  
      {
          _age = age;
      }
                         
      public override void Process(ref IMethodCallMessage message)         
	  {             
          int ageValue = -1;             
		  if (message.Args != null && message.Args[0] != null)                 
				ageValue = (int)message.Args[0];             
					
		  if (ageValue > _age)
          {
                throw new Exception(String.Format("Yaş en çok {0} olabilir.{1} geçerli bir yaş değil.", _age, ageValue));
           }

       }
}

Bu yaratmış olduğumuz ilgileri bir önceki yazıda oluşturduğumuz alt yapının algılayabileceği şekilde bir kaç değişiklik yapmamız gerekecek. Bunun için aşağıdaki değişikliği Control sınıfımıza eklememiz yeterli olacaktır.

    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)
        {
            if (msg is IMethodCallMessage)
            {
                IMethodCallMessage preCall = (msg as IMethodCallMessage);
                this.BeforeMessageCall(ref preCall);//BeforeMessageCall diye bir metod yaratıyoruz.

            }
            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);
        }

        //BeforeMessageCall metodu ile nesne yaratmadan önce,metod çağırmadan önce
        //ya da bir özelliği çağırmadan önce yapacağımız işlemleri çağırıyoruz.
        //Burada BaseAspect diye Attribute'ten türetilen kendi nesne tipimizi kontrol ediyoruz.
        //Eğer gelen mesajın özellikleri bu BaseAspect tipindense onun Process methodunu çağırıyoruz.
        //Burada hem metod hem de metod'un parametreleri var ise ve parametrelerde de BaseAspect tipi
        //olabilir mi diye kontrol ediyoruz.
        //-----------------------------------------------
        //Burada sadece method ve parametrelerini kontrol ediyoruz. Burada ki yapıyı genişleterek
        // daha güzel bir içerik yapabiliriz.
        private void BeforeMessageCall(ref IMethodCallMessage msg)
        {
            //Methodun
            BaseAspect[] attributes = msg.MethodBase.GetCustomAttributes(typeof(BaseAspect), true) as BaseAspect[];
            foreach (BaseAspect item in attributes)
            {
                item.Process(ref msg);
            }

            //Metodun parametrelerini alıyoruz.
            ParameterInfo[] parameters = msg.MethodBase.GetParameters();
            foreach (ParameterInfo item in parameters)
            {
                //Parametre değerlerinde BaseAspect'ten türeyen bir Attribute var mı kontrol ediyoruz.
                //Var ise metod çağrılmadan önce sahip olduğu Attribute çağırılıyor.
                attributes = item.GetCustomAttributes(typeof(BaseAspect), true) as BaseAspect[];
                foreach (BaseAspect parameterAttribute in attributes)
                {
                    parameterAttribute.Process(ref msg);
                }
            }

        }
   }

Bütün bunları yaptıktan sonra elimizde ne var kısaca bir özetleyelim; metodlarımızı, nesnelerimizi ve nesnelerin özelliklerini çağırmadan araya girebileceğimiz bir alt yapı, bu alt yapı içerisinde çeşitli sınıfları çalıştırmamızı sağlayacak metodlar ve çeşitli sınıflarımız(ilgilerimiz ya da “Aspect”lerimiz)

Şimdi bu yarattığımız ilgileri Person sınıfında kullanalım. Bunun için aşağıdaki gibi eklentiler yapmamız gerekmekte. Bu eklentiler aslında bir yazılım projesinde her kısımda ortak olarak kullanılabilecek ilgileri temsil ediyor. Log’lama gibi…Ya da belli bir değerleri kontrol eden validasyonlar gibi…Aşağıdaki örnek bu iki kavram için yeterli olur umarım.

    [Controller]
    public class Person : ContextBoundObject
    {
        private int _age = 0;
        private string _name = "";

        public string Name { get { return _name; } }

        public Person(string name) : base() { _name = name; }

        public int Age
        {

            get
            {
                return _age;
            }
            [Logger]//Age özelliğine değer ataması yapmadan önce yine kayıt tutuyoruz
            [AgeValidation(150)]//Atanacak değerin kontrolünü AgeValidation ile kontrol ediyoruz
            set                 //Bu noktada bir insanın 150 yaşından büyük olamayacağını düşünüyoruz.
            {
                _age = value;
            }
        }

        //DoSomething metodu çağrılmadan önce metod ile ilgili kayıt tutuyoruz...
        [Logger]
        public string DoSomething()
        {

            string logMessage = "Person sınıfının DoSomething() methodu çalıştı";
            Console.WriteLine(logMessage);

            return "";
        }

        //DoAnotherthing metodu çağrılmadan önce yine kayıt tutuyoruz.
        //Bu sefer metodun 2 tane parametresi olduğuna dikkat edelim.
        [Logger]
        public void DoAnotherThing(int parameter1, string parameter2)
        {
            Console.WriteLine("Person sınıfının DoAnotherThing() methodu çalıştı");
        }

    }

Aşağıdaki gibi basit bir konsol uygulaması ile Person sınıfımızı kullanabiliriz. Dikkat edersiniz ki herhangi ekstra bir ilgi yok.

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

            Person arda = new Person("Arda Çetinkaya");
            arda.Age = 16;
            arda.DoSomething();
            arda.DoAnotherThing(5, "Test parametresi");

            Console.ReadLine();

        }
    }

Çalıştırdığımız zaman bu kodu, aşağıdaki gibi bir çıktı alıyor olacağız. Çıktıyı kontrol ettiğimizde metod çağrımlarından önce ilgilerin çalıştığını göreceğiz.

Uygulamada küçük bir deşiklik yaparak, başka bir çıktıyı da görelim isterseniz. Bunun için aşağıdaki Person sınıfındaki Age özelliğine 200 yazdığımızda programın çıktısı aşağıdaki gibi olacaktır ve yarattığımız AgeValidation ilgisine takılıp Exception fırlatacaktır.

Bu yoğun kodlar içerisinde umarım çok kaybolmamışınızdır ve biraz da olsa bazı şeyleri anlamaya yardımcı olmuştur umarım bu yazı…Ama henüz bitmedi…(: Bir küçük örnek ile AOP biraz daha iyi anlayacağız. Hatırlarsanız önce ki yazılarda nesnelerin bulunduğu duruma göre farklı özellikler kazanabileceğini söylemiştim. Aşağıdaki örnekte buna örnek veriyor olacağım.

    //Car adı altında arabayı simule eden bir nesnemiz var
    //Aldığı Person tipinde ki parametre ile Drive() metodu
    //sınıfın içeriğini oluşturuyor.
    [Controller]
    public class Car : ContextBoundObject
    {
        //Dikkat ederseniz CanDrive() Aspect'i ile parametrenin
        //bu metod için uygun olup olmadığını kontrol ediyoruz.
        //18 yaşını doldurmuş olmak şeklindeki bir iş kuralını
        //bu şekilde uygulayabiliriz.
        //Ama bildiğiniz üzere bu iş kuralı çeşitli ülkelerde
        //değişik bir şekilde çalışır.Bunun için de aşağıdaki
        //sınıfa göz atmakta fayda var...
        public virtual void Drive([CanDrive(18)]Person driver)
        {

            Console.WriteLine("Bas gaza...");
        }
    }

    //Car'dan türeyen yeni bir sınıfımız var. Amerikan arabasını
    //temsil eden bu sınıf türediği sınıfın Drive() metodunu eziyor.
    public class AmericanCar : Car
    {
        //Burada dikkat edecek olursanız CanDrive() Aspect'inin aldığı
        //değer 16...Malum ABD'de araba kullanabilme yaşı 16...
        public override void Drive([CanDrive(16)]Person driver)
        {

            Console.WriteLine("Amerika'da 16 yaşında araba kullanılabiliyor...Bas gaza...");
        }
    }

Yukarıdaki nesneleri canlandırabilecek şekilde konsol uygulamamızı günceliyor ve daha sonra çalıştırıyoruz…

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

            Person arda = new Person("Arda Çetinkaya");
            arda.Age = 17;
            arda.DoSomething();
            arda.DoAnotherThing(5, "Test parametresi");

            AmericanCar cadillac = new AmericanCar();
            cadillac.Drive(arda);

            Car anadol = new Car();
            anadol.Drive(arda);

            Console.ReadLine();

        }
    }

Dikkat ederseniz anadol adındaki değişkenimizin Drive() metoduna kadar sorunsuz çalışacaktır. cadillac tipindeki değişkenin Drive() metoduna parametre olarak verdiğimiz nesnemiz, Drive() metodu için gerekli ön şartı sağladığından onda bir sorun olmadığının altını çizmek isterim. Bu örnekte nesnelerin bulundukları duruma göre sahip oldukları Aspect’lerin değişebileceğini gördük. 17 yaşındaki bir insan Türkiye’de araba kullanamazken, ABD’de araba kullanabilir. Ama insan özellikleri aynıdır (: Bu şekilde nesnelerin özelliklerinin değişebileceği sistemlerde AOP yaklaşımlarını kullanmak, sistem içerisinde ki karmaşıklığı minimuma indirecektir.

Neyse çok daha fazla saçmalamadan bitiriyorum. Umarım bazı şeyler biraz daha netleşmiştir. Biraz karmaşık bir konu ve aslında daha çok konuşacak şey var. Burada bahsetmiş olduğum kodların sadece bir örnek olduğu lütfen aklınızdan çıkarmayın. Geliştirin,hatta geliştirirken benle de paylaşırsanız sevinirim. Bir de .NET tarafında şu anda tam olarak bir AOP desteğinin olmadığı hatırlatmak isterim. Ama .NET’de AOP yaklaşımlarını uygulayabileceğiniz alt yapıyı biraz olsun yaratabileceğinizi unutmayın. Unity’i bu bağlamda kurcalamakta fayda olabilir. Tam olmasa da yaklaşımları AOP ile hemen hemen aynı…

Şimdilik bu kadar…Lütfen her türlü düşünce ve sorunuzu paylaşmaktan çekinmeyin…