Burak Selim Senyurt(MVP)
Matematik Mühendisi bir .NET Severin Yazıları...

Interpreter Tasarım Kalıbı - İkinci Randevu

Pazar, 16 Ağustos 2009 05:37 by bsenyurt

Merhaba Arkadaşlar,

Bir süre önce tasarım kalıplarından Interpreter desenini incelemiş ve konu ile ilişkili bir kural motorunun çok basit anlamda nasıl yazılabileceğini araştıracağımızdan bahsetmiştik. Interpreter tasarım kalıbında hatırlayacağınız gibi Terminal ve NonTerminal tipleri bulunmaktadır. NonTerminal tipler genellikle kural motoru gibi modellerde devreye girmektedir. Kural motorlarında(Rule Engine), işletilmek istenen ifadelerin içerisinde sıklıkla operatörlerin kullanılması söz konusudur.

 

Örneğin and, or, >=, <, küçüktür, eşittir gibi düşünebiliriz. Dikkat ederseniz eşittir ve küçüktür gibi kelimeleri de operatörler arasına kattım. Nitekim yorumlanacak ifade(Expression) bütününü kendimiz oluşturduğumuz için istediğimiz terimleri seçmemiz son derece doğaldır. Tam bu noktada sağ üstteki resmin konu ile ne alakası olduğunu düşünebilirsiniz. Laughing

Aslında bu yazımızdaki amacımız, içerisinde değişik renklerde misketleri barındıran bir kutu(ki örneğimizde string tipten generic bir koleksiyon olarak ifade edilecek) üzerinde, string bazlı mantıksal bir ifadeyi işletmektir. Örnek olarak aşağıdaki gibi bir kural tanımladığımızı göz önüne alabiliriz.

"Kirmizi ve Mavi veya Mor"

Buna göre sepet içerisindeki misketlerin rengine göre yukarıdaki kurala uyan bir durum varsa bir takım işlemlerin yapılmasını veya yapılmamasını arzu ediyoruz. Aslında ne yapılması gerektiğinin şu aşamada bir önemi yok. Wink Çünkü önemli olan ilk aşama, yukarıdaki kuralı söz konusu misket sepeti üzerinde işletebilmek. Peki bunu nasıl yapacağız? Dahası yapmak için Interpreter tasarım kalıbını nasıl kullanacağız?

İlk etapta, kural ifadesi içerisindeki materyalleri göz önüne almamızda yarar var. Renkleri aslında tek bir Terminal tipi ile ifade edebiliriz. Nitekim renklerin ayrı ayrı yapacakları bir işlevsellik yok.(Elbetteki kural ifadesi içerisindeki bilgilerin gerçek hayat kural motorlarında ayrı ve farklı görevleri olabilir. Bu durumda her biri için ayrı Terminal tiplerinin tasarlanması gerekir) Diğer taraftan renkler arasında ve, veya olmak üzere iki mantıksal operatör yer almaktadır. İşte bunlar NonTerminal tipler olarak tasarlanmalıdır. Nitekim kendi içlerinde, Expression tiplerinden ikisini taşıyacaklardır ki mantıksal olarak ve, veya işlemleri gerçeklenebilsin. Tabi birde içerisinde parantezler bulunmayan bir kural ifadesi ile karşı karşıyayız. Kuralın

"Kirmizi ve (Mavi veya Mor)" olması ile

"(Kirmizi ve Mavi) veya Mor"

olmasının arasında işlem öncelikleri açısından farklılıklar bulunur. Önce parantez içlerini çalıştırmak gerekir. Tabi bizim örneğimizde parantezleri işin içerisine şu an için katmıyor olacağız. Ama size parantezleri işin içerisine katarak geliştirme yapmaya çalışmanızı şiddetle öneririm. Özellikle string biçimdeki kuralı ayrıştırırken çok zorlu bir yoldan geçeceğinizi garanti edebilirim. Öyleki kuralı bir arkadaşınız yanlışlıkla şöylede yazabilir.

"(Kirmizi ve ((Mavi veya Mor)"...Upsss! Wink

Peki biz kuralı nasıl ayrıştıralım. Aşağıdaki şekil bize bu anlamda bir fikir verebilir.

Aslında bunun programatik taraftaki karşılığını bir ifade ağacı(Expression Tree) olarak düşünebiliriz. Ancak yazacağımız kod içerisinde Interpreter tasarım kalıbının uygulanması dışında, bu şekilde bir ifade ağacının çıkartılabilmesi için Recursive bir fonksiyonada ihtiyacımız olacaktır. Ta ta ta taaa...Sealed

(Kişisel Notum : Uzun yıllar çalıştığım eğitim firmasında verdiğim .Net derslerinde, Recursive metodları anlatırken çoğunlukla Faktoryel hesabı veya Fibonacci sayılarının bulunması problemlerini dile getirdiğimi hatırlıyorum da...Gerçek hayat çok ama çok daha farklı...Geniş düşünmek, vizyonu her zaman geniş tutmak gerekiyor. Çoğu zaman göz ardı ettiğiniz bir kavram, aslında bir problemin çözümünde kritik bir rol üstlenebiliyor. Recursive bir metodun örneğimizdeki ifade ağacının çıkartılmasında üstlendiği rolde olduğu gibi...)

Çünkü ifadenin n sayıda renk ve mantık operatörü içermesi söz konusudur. Bu durumda ifade ağacı oluşturulurken ve çalıştırılırken, bir önceki ifadeyi üreten ve bunu sonraki ifadeyi üretmek için girdi olarak kullanan bir fonksiyon yazılması şarttır. Artık örneğimizi geliştirmeye ne dersiniz? Şimdi aşağıdaki sınıf diagramı ve kodları içeren Console uygulamasını yazdığımızı düşünelim.

using System;
using System.Collections.Generic;

namespace Interpreter
{
    // Expression Type
    abstract class RuleExpression
    {  
        public abstract bool Interpret(List<string> context);
    }

    #region Terminal Expression Types

    class ArgumentExpression
    : RuleExpression
    {
        public string Name { get; set; }

        public override bool Interpret(List<string> context)
        {
            if(context.Contains(Name))
                return true;
            else
                return false;
        }
    }

    #endregion

    #region NonTerminal Expression Types

    class AndExpression
        : RuleExpression
    {
        public RuleExpression Left { get; set; }
        public RuleExpression Right { get; set; }

        public override bool Interpret(List<string> context)
        {
            return Left.Interpret(context) && Right.Interpret(context);
        }
    }

    class OrExpression
        : RuleExpression
    {
        public RuleExpression Left { get; set; }
        public RuleExpression Right { get; set; }

        public override bool Interpret(List<string> context)
        {
            return Left.Interpret(context) || Right.Interpret(context);
        }
    }

    #endregion

    // Expression ağacını oluşturmak ve çaşlıştırmakla görevli olan sınıf
    class RuleComputer
    {
        public List<RuleExpression> Expressions { get; set; }

        public RuleComputer()
        {
            Expressions = new List<RuleExpression>();
        }

        // Expression ağacının oluşturucusu ve çalıştırıcısı olan metoddur
        public bool RunExpressionTree(string ruleSyntax,List<string> context)
        {           
            bool result = false;
           
            // Önce kural metni içerisindeki boşluklara göre elemanlar ayrılır
            string[] ruleParts = ruleSyntax.Split(' ');

            // Küçük bir kontrol. Ancak fazlasınıda yapmak gerekir :) Yazılan kural metninin geçerli olup olmadığı denetlenmelidir.
            if (ruleParts.Length < 3)
                throw new Exception("Eleman sayısı kural için yeterli değildir");

            // Expression Tree oluşturulmasına başlanır(Recursive fonksiyonu kullandığımıza dikkat edelim)
            RuleExpression longExpression = Recursive(ruleParts, 1, null);
            // Expression ağacı koleksiyona eklenir
            Expressions.Add(longExpression);

            // Koleksiyondaki her bir Expression için Interpret operasyonu çalıştırılır
            foreach (RuleExpression expression in Expressions)
            {
                result = expression.Interpret(context);
            }

            return result;
        }

        // Expression ağacının oluşturulması için kullanılan recursive fonksiyon
        // Kuralı işletmek için en soldaki ikili daldan başlayarak sağa doğru ilerliyoruz
        RuleExpression Recursive(string[] parts, int step, RuleExpression expression)
        {          
            if (step == 1) // Soldan ilk operatör ile karşılaşıldığında
            {
                if (parts[step] == "ve")
                {
                    expression = new AndExpression { Left = new ArgumentExpression { Name = parts[step - 1] }, Right = new ArgumentExpression { Name = parts[step + 1] } };
                }
                if (parts[step] == "veya")
                {
                    expression = new OrExpression { Left = new ArgumentExpression { Name = parts[step - 1] }, Right = new ArgumentExpression { Name = parts[step + 1] } };
                }
            }
            else // İlk çift içerisindeki operator geçildikten sonra, her zaman bir önceki dalın, sonradan gelen argüman ile ve/veya işlemine sokulması sağlanır
            {
                if (parts[step] == "ve")
                {
                    expression = new AndExpression { Left = expression, Right = new ArgumentExpression { Name = parts[step + 1] } };
                }
                if (parts[step] == "veya")
                {
                    expression = new OrExpression { Left = expression, Right = new ArgumentExpression { Name = parts[step + 1] } };
                }
            }

            // Recursive metoddan bir notkada çıkılması gerekecektir. Bu çıkış noktası, son operatör ele alındıktan sonrasıdır.
            if (step == parts.Length - 2)
                return expression;

            // Öteleme yapılarak sonraki çifti almak üzere aynı metod tekrar işletilir
            return Recursive(parts, step + 2, expression);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // Örnek kural
            string rule = "Kirmizi ve Mavi veya Mor ve Siyah";

            // Kuralın denetleneceğin veri içeriği (Context)
            List<string> myBasket =new List<string> { "Yesil", "Kahverengi", "Lacivert", "Sari", "Mor", "Siyah" };
            RuleComputer computer = new RuleComputer();

            // Kirmizi ve Mavi = 0 && 0 => 0
            // 0 veya Mor = 0 || 1 => 1
            // 1 ve Siyah = 1 && 1 => 1
            bool result=computer.RunExpressionTree(rule,myBasket);
            Console.WriteLine(result);

            // Kirmizi ve Mavi = 0 && 0 => 0
            // 0 veya Mor = 0 || 0 => 0
            // 0 ve Siyah = 0 && 0 => 0
            myBasket = new List<string> { "Yesil", "Kahve", "Lacivert", "Beyaz" };
            Console.WriteLine(computer.RunExpressionTree(rule,myBasket));

            // Kuralı değiştirelim
            rule = "Kirmizi veya Beyaz";

            // Kirmizi veya Beyaz = 0 || 1 => 1
            Console.WriteLine(computer.RunExpressionTree(rule,myBasket));

            // Exception testidir
            // rule = "Sari";
            // Console.WriteLine(computer.RunExpressionTree(rule, myBasket));
        }
    }
}

Kodu dikkatlice incelemenizi öneririm.

Tasarım kalıbımıza göre, AndExpression ve OrExpression tipleri kural içerisindeki ve, veya terimlerini ifade etmektedir. Diğer taraftan renklerin her birini ArgumentExpression tipi ile temsil ediyoruz. AndExpression ve OrExpression tipleri aynı zamanda kendi sol ve sağ taraflarındaki nesneleri kullanabilmek için RuleExpression tipinden referansları kullanıyorlar. Kodun belkide en önemli tipi RuleComputer sınıfı.

Tabir yerinde ise, Interpreter kalıbının önüne geçtiğini söyleyebiliriz. RuleComputer içerisinde yer alan RunExpressionTree metodu, ifade ağacının oluşturulması ve çalıştırılmasından sorumludur. Bu metodda kendi içerisinde Recursive olan başka bir fonksiyonu çağırmaktadır. Yazımızın başlarında hatırlayacağınız üzere örnek bir kuralı soldan sağa doğru yorumlayarak ele aldığımızı görmüştük. Burada kuralın n sayıda argüman ve operatörden oluşturulması söz konusu olduğundan, ifade ağacının çıkartılmasının tek yolu kendi kendini çağıran ve bir önceki çağırımda oluşturduğu ifadeyi kullanan bir metod yazmaktır.

Main metodu içerisinde bir kaç test kuralı yazıldığını ve işletildiğini görmekteyiz. Kuralları işletiş şekline göre, ArgumentExpression tipine ait Interpret metodu içerisinde yaptğımız tek şey, parametre olarak gelen Context(yani renk bilgilerini içeren generic List koleksiyonu) içerisinde, söz konusu referansın taşıdığı rengin olup olmadığına bakmak ve buna göre geriye true veya false sonuç döndürmektir.

Uygulamamızı debug ederek çalıştırdığımızda ise son derece güzel noktalara ulaştığımızı görebiliriz Söz gelimi ilk kuralın işletilmesi sırasında RuleComputer içerisindeki Expressions özelliğinin aşağıdaki yapıda olduğunu hemen farkedebiliriz.

Dikkat edileceği üzere string tabanlı yazılan basit kuralın her bir parçası Exrpression Tree üzerinde nesnel olarak yerini almış ve birbirlerine bağlanmıştır. Bundan sonrasında kodun yapması gereken tek şey, ağacı ilk elemandan sonuncuya kadar dolaşmak ve tüm gördüğü RuleExpression türevli tipler için Interpret metodlarını çağırmaktır. Ve işte çalışma zamanı sonucu;

Peki neler yapamıyoruz?

  • Herşeyden önce sadece ve, veya operasyonlarına hizmet veren bir sistem söz konusu. Buna ancak operatörünüde ekleyebiliriz.
  • Diğer yandan, parantez yazımına destek verilmesi söz konusu olabilir. Bu duruma Expression Tree' nin oluşturulması sırasında parantez kullanımlarını değerlendirmemiz gerekecektir.
  • Kural olarak yazılan ifade bütününün, gerçekten doğru bir stilde yazıldığını denetlemek gerekir. Bitişik yazımlar yada tanımlı olmayan bir operatör(ve yerine yahu yazmış olabiliriz Wink ) hatalara neden olabilir.
  • ...

Maddeler elbetteki çoğaltılabilir. Ancak sonuçta ulaştığımız noktalardan birisi, belirli bir Context üzerinde, bizim belirlediğimiz bir kuralın işletilmesi ve sonuç olarak true yada false değere indirgenebilen bir çıktının ürettirilebilmesidir. Bir başka deyişle bu yapıyı esnetmek(örneğin true/ false haricinde diğer tiplerin üretimine destek vermek yada =, != gibi çift taraflı karşılaştırma operasyonları hesaba katabilmek...) tamamen klavyenin başındaki geliştiricini hayal gücü ile sınırlıdır. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

InterpreterV2.rar (26,55 kb)

Tasarım Desenleri - State

Perşembe, 6 Ağustos 2009 21:00 by bsenyurt

Merhaba Arkadaşlar,

Bir süre öncesine kadar özel bir bankada uzman yazılım geliştirici olarak görev almaktaydım. Bankada en çok hoşuma giden bazende en çok nefret ettiğim hususlardan biriside otomat makinesi idi. Smile Makineyi severdim çünkü fazla mesai yapıldığı hallerde içerisinde son derece işe yarar tuzlu ve tatlı gıdalar olurdu. Makinenin başına geçer, yemek istediğim ürüne bakar karar verdikten sonra ise gerekli miktarı makineye atardım. Sonra almak istediğim ürünün kodunu tuşlardım. Makine, ürünü benim için ilgili yerden aşağıya doğru ittirerek sunardı. Sonrada ürünü afiyetle yerdim. Ama makinenin şu huyunada çok kızardım. Para üstü vermezdi Undecided Eksiği bazı ürünler ile tamamlardım yada tamamlayamazdım. Çünkü eksik kalan kısma verilebilecek bir ürün olmazdı. Yine mesai yaptığım akşamların birisinde makineye gittim, ürüne karar verdim, paraları attım ve makine bip, bap, bup dedikten sonra öylece kala kaldım.

Çünkü makine sözüm ona ürünü vermişti. Ancak makinenin alt sepetinde ürün yoktu. Nitekim ürün tam bulunduğu cepten aşağıya doğru düşmek üzereyken oracıkta takılıvermişti. Para gitmişti, nitekim makinin dijital kısmında Teşekkürler yazıyordu Sealed Ben olaya klasik bir insan piskolojisi ile yaklaştım. Makineyi öne arkaya itekleyerek ürünü takıldığı yerden düşürttüm ve afiyetle yedim. Makineye pis pis bakarken aklıma şunlar geldi. Makineye yaklaşırken durağandı. Öylece birbirimize bakıyorduk. Sonra paramı attıp ürünü seçtiğimde makine bir dizi kontrol yaptı ve hazırlık moduna geçti. Ardından ürünü bana teslim etmek üzere kendi içerisindeki mekanikleri çalıştırdığında ürünü teslim etme modundaydı. Peki ürün takılıp bana veremediğinde hangi moddaydı da "Teşekkürler" diyip, paramı yutup, ürünü vermemişti Laughing Her neyse konumuz bu değil tabiki. Ama bu makinenin bu senaryo içerisinde anlattığım tüm durumları aslında yazılım terminolojisinde State Machine tipinden bir akış ile ifade edilebilmektedir. İşte bu günkü konumuz State tasarım kalıbı...

Davranışsal(Behavioral) tasarım desenlerinden olan State kalıbı, bir nesnenin içsel durumunda(Internal State) meydana gelecek değişimler sonrası çalışma zamanında dinamik olarak farklı davranışları sergileyebilmesini sağlayan bir model sunmaktadır. Aslında State tasarım kalıbını, Workflow terminolojisinde yer alan State Machine kavramının nesne yönelimli(Object Oriented) karşılığı olarak düşünebiliriz. Öyleki nesnenin durumunun değişmesi halinde farklı davranışlar sergilemesi, sahip olduğu fonksiyonların tetiklenmesi ve bunlar arasında duruma göre gerekli geçişlerin(Transitions) sağlanması anlamına da gelmektedir. Dolayısıyla, State Machine kavramına aşina olanlarımız için State desenini kavramak son derece kolaydır.

Farklı bir örnek ile devam edelim. Bu amaçla bir müşterinin sahip olduğu banka hesabının durumlarını göz önüne alabiliriz. Bakiyenin içeriği müşterinin para yatırmasına, çekmesine, faiz ödemesine, fon alıp satmasına vb... gibi aksiyonlara göre sürekli değişiklik gösterecektir. Bir başka deyişle hesabın kendi iç durumunda bir takım değişiklikler olması söz konusudur. Bu değişikliker oldukça hesabın farklı durumları olması(bir başka deyişle müşterinin farklı şekillerde değerlendirilmesi) gerekir. Örneğin fazla borçlanma nedeniyle farklı bir hesap durumu olmalıdır. Yada hesabın ilk açılmasında başlangıç durumu tesis edilmeli, standart faiz oranları belirlenmelidir(Aynen otomat makinesinin prize takıldığında ön hazırlıklar yaptığı sıradaki konumu gibi). Hatta müşterinin düzenli ödemelerinin ona ekstradan bir anlam katması sonucu, hesabının kolay kredi almaya uygun bir duruma geçmesi mümkün olabilir.

Yazılım tarafından olaya baktığımızda aslında State diagramları ile ifade edilebilen her nesne için State deseninin uygulanabileceğini düşünebiliriz. Örneğin uygulamanın çalıştığı makinenin bellek durumları State kalıbına uygun olarak tasarlanabilir. Makinin normal seviyede olması, sistem kaynaklarının çok tüketilmesi sonucu alarm haline geçmesi veya alarm verilmeden önce uyarı moduna geçmesi söz konusu olabilir. Bu durumlar arasındaki geçişler aslında bilgisayarın bazı iç değerlerine göre gerçeklenir. Memory, CPU, Running Process ölçümleri birer kriter olabilir ve örneğin Computer isimli bir nesnenin iç durumunu ifade edebilir.

Başka bir örnek olarak oyun programlarında yer alan bazı senaryoları verebiliriz. Söz gelimi RPG tipinden bir oyunda yer alan herhangibi kahramanı düşünelim. Bu kahramanın duruma göre savaşması veya bir takım kontrollerde bulunması gibi davranışları, State deseninden yararlanılarak modellenebilir. Öyleki, savaş halinde iken kahramanın tüm gücüyle çarpışması, aynı zamanda devriyede olması söz konusu iken, barış halinde savaşmaması ama devriyeye devam etmesi durumları söz konusudur. Bu durumların nesne yönelimli tarafta ifadesinde State kalıbından yararlanılır.

Aslında tüm bu örneklerde dikkat edilmesi gereken ortak bir notkada vardır. State tasarım kalıbında, durum değişmelerine neden olacak(yani davranışların farklılaşmasına) bir takım nesne içi değerler vardır. Bunların tamamı aslında davranış değişimi için takip edilecek içeriği oluşturmaktadır. Müşteri hesabı örneğinde Hesap(Account) asıl içeriği oluşturmaktadır. Bilgisayarın durumlarının ele alındığı örnekte makinenin kendisi asıl içeriği oluşturmaktadır. Hımmm... Bu durumda ortaya şöyle bir soru çıkmaktadır. İçeriğindeki veri değişimleri eğer bir nesnenin davranışlarını belirliyorsa, bu davranışların n sayıda olması ve içeriği sağlayan tip tarafından kullanılması nasıl sağlanabilir?

Bundan sonra internal state' i taşıyan nesneye Context dediğimizi düşünelim. Birden fazla davranış ve doğal olarak durum olabileceğinden, Context' in farklı durumlara erişebilip aralardaki geçişleri(Transitions) sağlayabilmesi gerekir. Bu durumda, Context tipinin tüm durumlar için ortak bir arayüz sunan başka bir tip ile(buna State diyebiliriz) Aggregation ilişkisini sağlaması uygundur. State tipinin kendisi aslında, Context tipinin belli bir durumu ile ilişkilendirilmiş davranışların kapsüllenmesi için bir arayüz sunmaktadır. Bu arayüz sunumu aslı durum tipleri(Concrete State) tarafından değerlendirilebilir. Aslında bu yazdıklarımızdan deseninin sınıf diagramını az çok hayal edebiliriz.

E haydi öyleyse basit bir örnek ile kalıbı kavramaya çalışalım. Senaryomuzda yazımızın başında bol bol kulakları çınlayan otomat makinesini ele alıyor olacağız. Laughing Tabiki amacımız kalıbın nasıl uygulandığını ele almak olduğundan mümkün olduğunca sade(her zamanki gibi) bir örnek geliştireceğiz. Otomat makinesi için olası durumları şu şekilde düşünebiliriz. Makine elektrik şalterinden açıldığında bazı ön hazırlıklar yapar. Bu zaman diliminde makine Initialize modundadır(InitializeState). Initialize işlemleri başarılı ise makine bekleme moduna geçer(WaitingState). Ne bekler? Tabiki bizden bir ürün almamızı Wink Müşteri bir ürün talep ettiğinde bunu almak için makineye para atması ve sonrasında seçimi bildirmesi gerekir. Bu işlemi Context tipimiz içerisindeki bir metodun üstlendiğini düşünebiliriz. Eğer atılan para yeterli ise ürünün hazırlanması moduna geçilir(PreparingState) ve işlem başarılı bir şekilde tamamlanırsa ürün teslim edilir(DeliveryState). Kısaca Context tipi olarak düşündüğümüz VendingMachine sınıfı için dört farklı durum(State) düşünüyoruz. İşte sınıf diagramımız;

ve kodlarımız

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace StatePattern
{
    // State tipi
    // abstract sınıf olabileceği gibi interface şeklinde de tasarlanabilir
    abstract class VendingMachineState
    {
        public abstract void HandleState(VendingMachine context);
    }

    // Concrete State tipi
    // Otomat start düğmesine basılarak çalıştırıldığında öncelikli olarak bir ön hazırlık yapacaktır.
    class InitializeState
        :VendingMachineState
    {
        public InitializeState()
        {
            Console.WriteLine("Initialize...");
        }
        public override void HandleState(VendingMachine context)
        {
            Console.WriteLine("Ön hazırlıklar yapılıyor");
            Thread.Sleep(2000);           
            // Makinenin durumu değiştiriliyor. Makine initialize edilmiş. Bekleme konumuna geçebilir.
            context.State=new WaitingState();    
        }
    }

    // Concrete State tipi
    class PreparingState : VendingMachineState
    {
        public PreparingState()
        {
            Console.WriteLine("Preparing...");
        }
        public override void HandleState(VendingMachine context)
        {
            Console.WriteLine("İstenilen ürün hazırlanıyor. Lütfen bekleyiniz");
            // Makienin durumu değiştiriliyor. Ürün hazırlanması bitmiş. Buna göre ürünü teslim etme durumuna geçiyor.
            context.State = new DeliveryState();
        }
    }

    // Concrete State tipi
    class WaitingState
        : VendingMachineState
    {
        public WaitingState()
        {
            Console.WriteLine("Waiting...");
        }       
        public override void HandleState(VendingMachine context)
        {
            int totalProduct=context.ProductList.Sum<Product>(p => p.Count);

            Console.WriteLine("Makine bekleme konumunda. Şu anda {0} adet ürün var.",totalProduct.ToString());
            // Makine bekleme konumundayken  aslında bir State değişikliği söz konusu değil. Değişimi sağlayacak olan aslında istemcinin vereceği bir aksiyon. Context tipi üzerindeki RequestProduct metodunun çağırılması bu anlamda düşünülebilir.
        }
    }

    // Concrete State tipi
    class DeliveryState
          : VendingMachineState
    {
        public DeliveryState()
        {
            Console.WriteLine("Delivering...");
        }
        public override void HandleState(VendingMachine context)
        {
            Console.WriteLine("Ürün teslim ediliyor");
            // Makinin durumu değiştiriliyor. Ürün teslim edildikten sonra tekrar bekleme konumuna alınıyor.
            context.State = new WaitingState();
        }
    }

    // Context tipi
    class VendingMachine
    {
        public List<Product> ProductList = new List<Product>();
        // Context tipi, kendi içerisinde State nesne referanslarını değiştirebilir. Bunun için State tipinden bir özellik sunmaktadır
        private VendingMachineState _state;

        public VendingMachineState State
        {
            get { return _state; }
            set
            {
                // State değiştiğinde, üretilen State nesne örneğinin çalışma zamanındaki referansına ait HandleState metodu çalıştırılır. Parametre olarak o anki Context gönderilir.
                _state = value;
                // Burada durum değişimleri sonucu çalıştırılacak davranışların başlatılma noktasınıda merkezileştirmiş oluyoruz.
                _state.HandleState(this);
            }
        }       

        // Context nesnesi örneklenirken başlangıç durumu belirtilir.
        public VendingMachine()
        {
            // Test için makineye örnek ürünler yüklenir.
            ProductList.Add(new Product { Name = "Çikolata K", ListPrice = 10,Count=50 });
            ProductList.Add(new Product { Name = "Biskuvi Bis", ListPrice = 3.45 ,Count=50});
            ProductList.Add(new Product { Name = "Tuzlu mu tuzlu çıtır", ListPrice = 4.50 ,Count=35});

            // Makineye ürünleri yükledikten sonra durumunu değiştir
            State = new InitializeState();
        }
        public void RequestProduct(string productName,double money)
        {
            Console.WriteLine("Ürün siparişi geldi. {0} için atılan para : {1}",productName,money);
            Product prd = (from p in ProductList
                           where (p.Name == productName && (money >= p.ListPrice && p.Count >= 1))
                           select p).SingleOrDefault<Product>();
                       
            // Eğer talep edilen ürün stokta var ve atılan para yeterli ise
            if (prd != null)
            {
                prd.Count--;
                // Makinenin durumunu değiştir
                State = new PreparingState();
            }
            else
                State = new WaitingState();
        }
    }

    // Yardımcı tip
    class Product
    {
        public string Name { get; set; }
        public double ListPrice { get; set; }
        public int Count { get; set; }
    }

    // Client
    class Program
    {
        static void Main(string[] args)
        {
            // Context tipine ait nesne örneği oluşturulur
            VendingMachine machine = new VendingMachine();

            // İstemci bir ürün ister
            machine.RequestProduct("Çikolata K",10);
           
            machine.RequestProduct("Bsissi", 12); // Bu ürün olmadığı için vermeyecektir. Herhangibir aksiyon alınmayacaktır.
        }
    }
}

Örneğimizde, Client yani müşteri makineyi çalıştırarak işe başlıyor. Bir başka deyişle VendingMachineI(Context) tipinden bir nesne örneği oluşturuluyor. Bu nesne ayağa kalkarken içerisindeki bir listeye 3 farklı üründen değişik miktarlarda aktarıyor. Bu noktada VendingMachine nesnesinin durumlarında da değişmeler oluyor. Sonrasında, machine isimli nesne örneği üzerinden RequestProduct metodu çağırılıyor. Yani müşteri makineden bir ürün istiyor. Bu sırada, yine makinenin durumları arasında bazı geçişler oluyor. Özet olarak makinenin iç durumunda yapılan bazı değişikliklere göre farklı durumlara geçmesi ve farklı davranışların sergilenmesi sağlanıyor.

Ben geliştirdiğimiz örnekte pek çok durumuda göz ardı ettim. Embarassed Örneğin makinede talep edilen ürünün olmaması, atılan paranın yetersiz kalması veya fazla gelmesi yada makinin fişten çekilmesi hali...Bu olaylar gerçkeştiğindede aslında makinenin farklı durumlara geçmesi ve dolayısıyla Context tipinin farklı davranışlar sergilemesi gerekebilir. Bu kısımları, bir desen uyguladığımız için sisteme eklememiz aslında son derece basittir. Örneğimizi çalıştırdığımızda aşağıdakine benzer bir sonuç ile karşılaştığımızı görebiliriz.

Tabiki yukarıdaki gibi bir deseni uygulamak yerine her şeyi if veya switch gibi kontrol deyimleri ile ele almaya çalışabiliriz. Tabi bu durumda hem kodun karmaşıklaşmasına neden olur hemde genişletilebilirliğini zorlaştırmış oluruz. Nitekim şu anda uygulanan desene göre, makine için yeni bir davranış eklemek aslında State arayüzünden türüyen bir tip ekleyip bunu ilgili yerlerde değerlendirmekten başka bir işlem değildir. Bunu daha iyi anlamak için aynı örneği if ve switch yapıları ile geliştirmeye çalışmalısınız. Böylece geldik bir tasarım deseninin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

StatePattern.rar (26,49 kb)

Tasarım Desenleri - Interpreter

Pazartesi, 3 Ağustos 2009 18:30 by bsenyurt

Yandaki legoya baktığımızda sanıyorum ki hepimizin aklına Romalılar gelmektedir. Aslında benim aklıma Ben Hur filmi ve müthiş atlı araba yarışı sahneleri geliyor. Her neyse...

Romalılar, Mısırlıların fikirlerinden yola çıkarak harfler ile ifade edilebilen bir sayı sistemini geliştirmiştir. Bu nedenle mutlaka okul hayatımızın bir döneminde Roma Rakamları ile karşılaşmışızdır. Aslında son derece eğlenceli bir sayı sistemidir ve bazı filmlerin sonunda, çevrildikleri yıllar genellikle Roma rakamları ile ifade edilmektedir.  

Tabi Romalılar, geliştirdikleri bu sayı sistemlerinin bir gün gelipte GOF' un tasarım kalıplarından birisine ilham vereceklerini eminimki düşünmemiştir. (Gerçi yazılım teknolojilerindeki pek çok sorunsalın çözümünde tarihten dersler alınmıştır. Örneğin Microsoft Solution Framework(MSF) eğitim materyallerinde Kartaca savaşından bahsedildiğini çok iyi hatırlarım Wink ) İlhamı alan desen Interpreter tasarım kalıbıdır. Aslında kalıbın amacını anlamak için örnek senaryolara bir bakalım.

Diyelim ki çalışma ortamımız içerisinde şöyle bir bilgi yer alıyor. "MDCLXIV". Hatta bu bilgi, tarihsel kaynaklar ile ilişkili bir veritabanı giriş ekranından aynen bu metin formatında geliyor olsun. Ne varki, bu string bilgi yerine sayısal karşılığının bulunmasının daha önemli olduğu açıktır. Nitekim sayısal değer olması halinde bazı tarih bazlı hesaplamalar daha kolay yapılabilecektir. MDCLXIV değerinin karşılığı aslında 1664' tür. Nasıl mı? Aşağıdaki satıra geçmeden önce kağıt ve kalemi alıp hatırlamaya ve çözmeye çalışın Wink

MDCLXVI = (M=1000)+(D=500)+(C=100)+(L=50)+(X=10)+(V=5)-(I=1) = 1664

Çok güzel. Peki programatik ortamda bu tip bir ifadeyi kim, nasıl yorumlayacaktır? İşte Interpreter tasarım kalıbının ana fikri bu tip ifadelerden oluşan bazı özel veri gramerlerini yorumlayabilecek bir yapının oluşturulması için bir model sunmaktır. Bu örnek son derece popülerdir. Pek çok kaynakta(başta DoFactory ve OODesign) Roma rakamlarının sayıya dönüştürülmesi işlemlerinin tasarım kalıbına örnek bir senaryo olarak sunulduğunu görebilirsiniz.

Hemen konuya farklı bir örnekle devam edelim. Söz gelimi günün tarihini sistemde "MM - DD - YYYY" şeklinde elde etmek istediğimizi farzedelim(Tabi hile yapıp DateTime fonksiyonlarını kullanmıyoruz). Bunun için günün tarihini, formatta gösterilen şekilde sunmamız yeterli olacaktır. Ama olayı birde şu açıdan ele alalım. Burada tarih bilgisi için bir veri gramerimiz olduğunu düşünelim. Buna göre, günün tarihini sistemin o anki ihtiyaçları doğrultusunda "MM - DD - YYYY", "DD - MM - YYYY", "D - MMMM - YY", "DD - YYYY", "MMMM - YY" vb formatlara göre elde etmekte isteyebiliriz. Şimdi durum biraz değişti sanırım Wink Hımmm... Bu durumda programatik tarafta ayrı ayrı parser yazmak çok da mantıklı olmayacaktır. Ne yapılabilir? Interpreter deseni ile bu tarih gramerini kolayca yorumlayabilir ve günün tarihinin istediğimiz formatta sunulmasını sağlayabiliriz.

Elbette başka örneklerde vermek mümkündür. Söz gelimi çok geliştirilmemekle birlikte kural işletme motorları(Rule Engine) söz konusu tasarım kalıbını sıklıkla kullanırlar. Buna göre kuralı oluşturan ifadeler ayrıştırılarak yorumlanır ve genellikle boolean(true/false) sonuçlar üretilir. Bir başka deyişle içeriğin, tanımlanan bir kurala uygun olup olmadığının kontrolü yapılır. Uygunluk true anlamındadır. Tabi desenin uygulanış biçiminde mantıksal değerlerin üretilmesi zorunlu değildir. Roma rakamı örneğinde bu açıkça görülmektedir.

Oldukça heyecan uyandıran bir desen olmasına karşılık, şekli belirli ve düzgün olan gramer ifadelerinde(Formal Grammer) değerlendirildiği için kısıtlı bir kullanım alanı söz konusudur. Sanıyorumki dofactory.com sitesinde desenin kullanım oranının neden %20' ler seviyesinde kaldığını bu cümle açıklamaktadır. Dilerseniz heyecanımızı kırmayalım ve desene ait UML şeması ile yolumuza devam edelim.

Aktörlerimizden Context, yorumlanacak içeriği taşımaktadır. Genellikle ifade bütününü ve yorumlama sonucunu kendi içerisinde taşıyan bir tip olarak düşünülebilir. Context içerisinde değerlendirilmesi gereken her bir parçanın yorumlanması operasyonunu ise AbstractExpression tipi sunmaktadır. Bu tip, abstract class olarak tasarlanır ve grameri yorumlama kısımlarına ilişkin ana iş mantığını(Business Logic) üstlenebilir. Bazı durumlarda interface olarak tasarlandığınıda ve yorumlama işinin kendisinden türeyen tiplere bırakıldığını görebiliriz. UML şemasında dikkat çekici noktalardan birisi TerminalExpression ile NonterminalExpression isimli iki farklı Exrepssion tipi olmasıdır.

Aslında burada örnekler üzerinden hareket etmemizde yarar vardır. Roma rakamları örneğimizde her bir harf aslında TerminalExpression tipinden sınıflar içerisinde değerlendirilir. Ancak bir kural motorunda, ifadeler arasında bazı semboller ile işlemler yapılması gerekiyor olabilir. Örneğin iki Terminal(yada Nonterminal) ifadenin or veya and ile bağlanması yada matematiksel operasyona sokulması gibi. İşte bu tip durumlarda kullanılan ara semboller(and, or, + vb...) NonterminalExpression tipleri içerisinde değerlendirilir. Şemadan görüleceği üzere, NonTerminalExpression tipinden AbstractExpression tipine doğru tanımlanmış bir Aggregation ilişki söz konusudur. Yani NonTerminalExpression kendi içerisinde AbstractExpression türevli referansları taşıyabilmelidir. Bu gereklidir, nitekim NonTerminal sembollerin Terminal ifadeleri üzerinde değerlendirilmesi söz konusudur. Söz gelimi and operasyonunun uygulanması istenen durumlarda iki adet operand'ın(TerminalExpression) olması gerekir. Bunlar NonTerminalExpression içerisinde birer üye olarak bulunmalı ve initialize edilmelidirler. Bu nedenle bir Aggregation ilişkisi söz konusudur.

Artık bir örnek ile devam edebiliriz. Senaryo bulmak konusunda sıkıntımız olsada amacımızın desenin nasıl uygulandığını kavramak olduğunu bir kez daha hatırlatalım. Konuyu kolay bir şekilde ele almak için NonTerminalExpression nesnelerini hesaba katmayacağız. Elimizde bir projenin içerisinde yer alan çalışanların sembolik tanımlamalarını string bazlı taşıyan bir Context tipi olduğunu düşünelim. Örneğin mimarlar için A, danışmanlar için C, uzman geliştiriciler için S ve geliştiriciler için D harflerini göz önüne alabiliriz. Buna göre, örneğin ACSSDDDD şeklindeki bir metnin bizim için anlamı,

ACSSDDDD = 1 Architecture + 1 Consultant + 2 Senior Developer + 4 Junior Developer

şeklinde olacaktır.

Bu metinsel bilgidende bir projenin adam başı maliyetini çıkartabildiğimizi düşünebiliriz. Dolayısıyla string ifadeyi tarayıp bize sayısal değer döndürecek bir yorumlayıcı modele ihtiyacımız var(Burada string bir içeriği basit olarak alıp parse etmeyi hedeflemediğimizi belirtelim). Buna göre A, C, S ve D harflerinin aslında birer TerminalExpression tipi olarak ifade edilebileceğini düşünebiliriz. İşte örneğimize ait sınıf diagramımız,

ve kod içeriğimiz.

using System;
using System.Collections.Generic;

namespace Interpreter
{
    // Context class
    class Context
    {
        public string Formula { get; set; }
        public int TotalPoint { get; set; }
    }

    // Expression
    abstract class RoleExpression
    {
        public abstract void Interpret(Context context);
    }

    #region Terminal Expression Sınıfları

    // TerminalExpression
    class ArchitectureExpression
        : RoleExpression
    {
        public override void Interpret(Context context)
        {
            if (context.Formula.Contains("A"))
            {
                context.TotalPoint += 5;
            }
        }
    }

    // TerminalExpression
    class ConsultantExpression
        : RoleExpression
    {
        public override void Interpret(Context context)
        {
            if (context.Formula.Contains("C"))
                context.TotalPoint += 10;
        }
    }

    // TerminalExpression
    class SeniorExpression
        : RoleExpression
    {
        public override void Interpret(Context context)
        {
            if (context.Formula.Contains("S"))
                context.TotalPoint += 15;
        }
    }

    // TerminalExpression
    class DeveloperExpression
        : RoleExpression
    {
        public override void Interpret(Context context)
        {
            if (context.Formula.Contains("D"))
                context.TotalPoint += 20;
        }
    }

    #endregion

    // Client
    class Program
    {
        static List<RoleExpression> CreateExpressionTree(string formula)
        {
            // Expression ağacı oluşturulur
            List<RoleExpression> tree = new List<RoleExpression>();

            foreach (char role in formula)
            {
                if (role == 'A')
                    tree.Add(new ArchitectureExpression());
                else if (role == 'S')
                    tree.Add(new SeniorExpression());
                else if (role == 'D')
                    tree.Add(new DeveloperExpression());
                else if (role == 'C')
                    tree.Add(new ConsultantExpression());
            }
            return tree;
        }

        static void RunExpression(Context context)
        {
            foreach (RoleExpression expression in CreateExpressionTree(context.Formula))
            {
                expression.Interpret(context); // TerminalExpression tiplerine ait harf sembolleri buradaki metod çağrısındada gönderilebilir.
            }
            Console.WriteLine("{0} için maliyet puanı {1}", context.Formula, context.TotalPoint);
        }

        static void Main(string[] args)
        {
            Console.WriteLine("Architecture = 5, Consultant=10, Senior=15,Developer=20\n");
            // 1 Architect, 1 Consultan, 2 Senior Developer , 4 Junior Developer
            Context context = new Context { Formula = "ACSSDDDD" };
            RunExpression(context);

            // 1 Consultant, 1 Senior Developer, 2 Developer
            context = new Context { Formula = "CSDD" };
            RunExpression(context);

            // 1 Consultant, 1 Senior Developer, 2 Developer
            context = new Context { Formula = "SD" };
            RunExpression(context);           
        }
    }
}

Dikkat edileceği üzere Main metodunda bir Expression Tree oluşturulmaktadır. Bu Expression Tree' nin modellenmesi için string ifade içerisindeki tüm harfler tek tek dolaşılır ve uygun olan TerminalExpression nesne örnekleri üretilip, ağaca eklenir. Sornasında ise ağaç içerisindeki her bir TerminalExpression üzerinden Interpret metodu çalıştırılarak bir yorumlama işleminin gerçekleştirilmesi sağlanır. Yorumlama işlemi bu örnek için, karşılaşılan harflere göre bir puanlamanın, Context nesne örneği içerisindeki TotalPoint özelliğine yansıtılmasıdır. İşte uygulamanın çalışmasının sonucu.

Yaptığımız bu basit yorumlayıcı sadece metinsel bir ifade bütününü yorumlayarak ele almıştır. Aslında yapılan iş, berlirli bir grameri alıp sınıflara dönüştürmekle alakalıdır. Bu dönüştürme işlemi sırasında devreye Interpreter kalıbının aktörleri girmektedir. Grammer içerisinde yer alan herhangibir parça aslında bir TerminalExpression veya NonTerminalExpression olarak birer sınıfa dönüşür ve AbstractExpression tipi içerisinde veya türevlerinde işlenir. Sonrasında ise istemci uygulama, bir Expression Tree bütününü, kullandığı bir Context tipi üzerinde çalıştırır.  Tabiki desenin bu basit uygulanış şekli dışında kural motorlarında olduğu gibi NonTerminalExpression tiplerininde işin içerisinde girdiği daha karmaşık uyarlamaları vardır. (Dikkat edeceğiniz üzere birleşik bir metin var. Arada boşluklar veya aritmetiksel operatörler yok) Bu uyarlamalardan basit bir örneğini ilerleyen blog yazılarımdan birisinde aktarmaya çalışıyor olacağım. Böylece geldik bir tasarım deseninin daha sonuna. Tekrardan görüşünceye dek hepinze mutlu günler dilerim.

Interpreter.rar (253,31 kb)

Tasarım Desenleri - Iterator

Cuma, 31 Temmuz 2009 00:52 by bsenyurt

Merhaba Arkadaşlar,

Küçüklüğümde pek çoğumuz gibi sahip olduğum bir pul koleksiyonum vardı. Halen daha sakladığım pullar bulunmaktadır. Hatta o zamanlarda, çocuklar posta aracılığıyla yurt dışından arkadaşlar edinir, birbirleriyle pul değiş tokuşu bile yaparlardı. Düşünsenize, hem yabancı dilinizi geliştiriyor hem pul koleksiyonunuzu genişletiyorsunuz.

Tabiki posta mesajlaşması biraz zaman alan bir mevzuydu. Bu günkü gibi sosyal içerikli portallar veya mesajlaşma cihazları ve daha nice gelişmiş teknoloji yoktu. Acaba bu devirde yaşayan çocuklardan kaçı pul koleksiyonu yapıyor Undecided Neyse bu duygusal ortamdan çıkalım hemen. Pul koleksiyonumda yaptığım işlerden birisi zaman zaman onları baştan sonra, yada sondan başa, yada ortadan bir yerden herhangibir yöne doğru gözle taramak olurdu. Bazen kendi kafama göre sıralarını değiştirirdim. Peki nesne yönelimli dillerde kullandığımız koleksiyon veya dizi gibi veri yapıları üzerindede bu ve benzer işlemleri yapmıyor muyuz? Çeşitli tipte veri yapılarında(Data Structures) dolaşıyor, içeriklerine bakıyoruz.

Koleksiyonlar, C# gibi bir programlama dilinde belkide en önemli veri yapılarından(Data Structures) birisidir. Bir koleksiyon kendi içerisinde farklı tipte veya aynı tipte nesneleri çeşitli formatlarda(List, Stack, Queue, Dictionary vb...) saklayabilen nesne bütünleri olarak düşünülebilir. Hatta bildiğiniz üzere .Net 2.0 ile birlikte C# ve Vb.Net tarafına kazandırılan generic yetenekler ile, koleksiyonların tip güvenli(Type Safety) olarak ele alınmalarıda garanti edilmiştir. Hatta, C# 3.0 ve Vb 9.0 ile birlikte neler olmuştur neler Wink Artık koleksiyonlar üzerinden LINQ sorguları yardımıyla sanki bir veritabanı tablosunu sorgularmışcasına filtrelemeler yapılabilmektedir. Ancak olayın en başından beri süre gelen ve bu yazımıza konu olan bir durumda söz konusudur. Bir koleksiyonun veya bir dizinin iç yapısını bilmeye gerek duymadan, başından sonunda kadar dolaşılabilmesi mümkündür. Dolayısıyla, koleksiyon veya dizi gibi bir nesne bütününün içerisindeki elemanlara erişilmesi ve dolaşılması noktasında devreye giren bir aktör olmalıdır. Aslında bu sorumluluk, bir öteleme nesnesine(Iterator Object) verilmiştir.

Bu açıdan bakıldığında nesne bütününün elemanlarına(çoğunlukla koleksiyon veya dizi olarak düşünebiliriz) erişilmesi, bu elemanların baştan sona dolaşılması, bir öteleme sırasında nerede kalındığının tutulması, hangi koşula göre devam edilmesi gerektiğinin bilinmesi, devam edilecek ise bir sonra gelen nesnenin döndürülmesi gibi sorumlulukları üstüne alan bir aktörden bahsetmekteyiz. Ki bu aktör aslında generic programlamada önemli bir yerede sahiptir. Nitekim, herhangibir nesne bütünün içinde dolaşılması için standart bir yol sunulması generic programlamanın gereksinimlerinden birisidir. Veri yapılarının ne kadar sık kullanıldığı düşünülünce doğal olarak ortaya, tasarımı kalıplaşmış bir uygulama biçimi çıkmaktadır. İşte bu yazımızın konusu, Behavioral(Davranışsal) kalıplardan olan Iterator tasarım deseni.

Tabiki programlama dillerine zaman içerisinde gelen bazı ek yetenekler sayesinde desenin uygulanış biçimi çok daha kolaylaşmıştır. Özellikle C# tarafında, 2.0 versiyonu ile birlikte gelen yield anahtar kelimesinin kullanımı, C# 3.0 ile birlikte LINQ(Language Integrated Query) özelliklerinin gelmesi aslında nesnelerin elemanları üzerinde bir uçtan diğerine farklı filtrelemelere göre hareket edilmesini son derece kolaylaştırmaktadır. C# tarafında bu konu ile ilişkili baş aktör IEnumerable arayüzüdür(Interface). Kendisi doğal yollardan Iterator deseninin uygulanabilir olmasını sağlamaktadır. Biz bu yaklaşımları yazımızın sonlarında değerlendireceğiz. Şimdilik desenimizi kuralına uygun olaraktan geliştireceğiz. Öncesinde iterator kalıbına örnek bir kaç senaryo üzerinde durmaya çalışalım.

Örneğin herhangibir bilgisayar sisteminde yer alan klasör yapısında bu desenin uygulanışını değerlendirebiliriz. Klasörler kendi içlerinde alt klasörler veya dosyalar içerir. Bunların ekrana beliri bir formatta listelenmesi sırasında o anki klasör ağacının tamamının bir uçtan diğerine dolaşılması gerekecektir. Ya da bir klasörün toplam boyutunun bulunması istendiğinde, alt klasör ve içlerindeki dosyaların boyutlarınında bir uçtan diğerine değerlendirilmesi gerekecektir. Buradaki klasör yapısı ve içeriği nesnel bazda düşünüldüğünde, çeşitli filtrelemelere göre değerlendirilebilmesinde sorumluluk, Iterator nesnesi tarafından üstlenilebilir. Unutulmaması gereken noktalardan birisi de, Iterator tasarım kalbında nesne bütünü içerisindeki elemanların nasıl yapılandırıldıklarının bir öneminin olmayışıdır. Desenin amacı söz konusu nesne bütünü baştan sona dolaşabilmektir. Bir başka örnek olarak bir şirketin organizasyonel yapısını ifade eden bir nesne bütünü göz önüne alınabilir. Ağaç yapısı şeklinde ifade edilebilecek bu nesne bütünün içerisinde hareket edilebilmesi sırasında dalların dizilişleri, alt dallarda kimlerin olduğu, nasıl hareket edilmesi gerektiği gibi kriterlerin sorumlulukları Iterator nesnesine yüklenebilir.

Şimdi basit bir örnekten ilerleyerek desenimizi kavramaya çalışalım. Ama öncesinde UML şemamıza bakmakta yarar olduğu kanısındayım.

Şekildende görüldüğü üzere Iterator nesnesi, öteleme sorumlulukları için bir arayüz sunmakta ve nesne kümesi içerisindeki hareketlilik sırasında yapılması gereken bazı operasyonları bildirmektedir. O anki nesnenin kim olduğunun bilinmesi için CurrentItem gibi bir metod(veya özellik-property olabilir) kullanılmaktadır. Bütün içerisindeki bir sonraki elemana geçmek için MoveNext metodu, takip eden nesne olup olmadğını tespit etmek için IsContinue metodu kullanılabilir. Yada iterasyona başlarken ilk elemana gitmek için First operasyonu ele alınabilir. Tabiki bu metodlar tamamen semboliktir. Nitekim IEnumerator arayüzüde kendi içerisinde buna benzer metodları sunmaktadır.

UML şemamıza baktığımızda, istemciden nesne bütününün kendisine(Aggregate object) ve Iterator tipine doğru bir Association tanımlandığını görmekteyiz. Sonuç olarak istemci tarafı Aggregate nesnesini kullanmakta ve içerisinde dolaşmak için Iterator örneklerinden yararlanmaktadır. Benzer şekilde iterasyon sorumluluğunu yerine getiren nesnede(Concrete Iterator) çok doğal olarak ConcreteAggregate nesnesinin üyelerine erişmekte ve kullanmaktadır. Yani ConcreteIterator' dan ConcreteAggregate' e doğru bir ilişki(Association) mevcuttur.

Artık örneğimizi tasarlamaya başlayabiliriz. Kalıbın nasıl uygulandığını görmek istediğimizden çok basit bir senaryo üzerinden gideceğiz. Senaryomuzda Product tipinden nesne örneklerini barındıran bir nesne bütünümüz olduğunu göz önüne alacağız. Buna göre Iterator tasarım kalbının kullanaraktan, ürünleri dolaşabilmek için bir Iterator nesnesinin nasıl geliştirilebileceğini ele alacağız. Sınıf diagramımız,

şeklinde olup kodlarımızda aşağıdaki gibidir.

using System;
using System.Collections;
using System.Collections.Generic;

namespace IteratorPattern
{
    // Item
    class Product
    {
        public int ProductId { get; set; }
        public string Name { get; set; }
        public decimal ListPrice { get; set; }

        public override string ToString()
        {
            return String.Format("{0} {1} {2}", ProductId.ToString(), Name, ListPrice.ToString("C2"));
        }
    }

    // Iterator
    // Nesne bütünü içerisindeki hareketlerin, yönlenmelerin gerçekleştirilebilmesi için gerekli operasyon arayüzünü tanımlar.
    interface IProductIterator
    {       
        Product First();
        Product MoveNext();
        bool IsContinue { get; }
        Product Current { get; }
    }

    // Aggregate
    // Nesne bütününün, iterasyon için Concrete Iterator tipinden nesne örneği döndürecek bir metodunun olmasını söyler.
    interface IProductCollection
    {
        IProductIterator GetIterator();
    }

    // Concrete Aggregate
    // Nesne kümesini barındıran tipimiz.
    class ProductCollection
        : IProductCollection
    {
        // Product topluluğunu saklamak için generic bir List<T> koleksiyonundan yardım alıyoruz.
        private List<Product> list = new List<Product>();

        // Ürün sayısını dışarıya vermek için kullanılan bir özellik
        public int ProductCount
        {
            get { return list.Count; }
        }

        // Eleman eklemek ve okumak için kullanılan bir Indeksleyici
        public Product this[int index]
        {
            get { return list[index]; }
            set { list.Add(value); }
        }
               
        #region IProductCollection Members

        // Iterator nesnesini örnekler
        public IProductIterator GetIterator()
        {
            // Iterator nesnesi örneklenirken parametre olarak o andaki ProductCollection nesne örneği referans olarak gönderilir.
            // Bu sayede ProductIterator isimli Concrete Iterator nesne örneği, çalışma zamanında hangi nesne bütününü dolaşacağını bilecektir.
            return new ProductIterator(this);
        }

        #endregion
    }

    // Concrete Iterator
    // Nesne bütününün bir ucundan diğerine hareket edilebilmesine olanak sağlayacak fonksiyonellikleri uygulayan asıl Iterator tipi
    class ProductIterator
        : IProductIterator
    {       
        // Iterator nesne örneğinin, çalışma zamanında hangi nesne bütününü dolaşacağını bilmesi gerekmektedir.
        private ProductCollection _books;
        private int _currentIndex = 0;
        // İstemci isterse adım sayısını değiştirebilir. Örneğin ikişer ikişer atlanarak gidilmesi sağlanabilir,
        public int StepSize { get; set; }

        // bu nedenle yapıcı metoda parametre olarak, ProductCollection(Concrete Aggregate) nesne örneğinin referansı gelir. Bu referansın GetIterator metodu içerisindeki çağrı ile gönderildiğini hatırlayalım.
        public ProductIterator(ProductCollection productCollection)
        {
            _books = productCollection;
        }
        #region IProductIterator Members

        // İlk elemana gidilmesini sağlayan metod
        public Product First()
        {
            // Nerede olunduğunun takibi için _currentIndex değeri set edilir
            _currentIndex = 0;
            return _books[0];
        }

        // Bir sonraki elemana geçilmesini sağlayan metod
        public Product MoveNext()
        {
            // Nerede olunduğunun takibi için _currentIndex değeri set edilir. Adım sayısı kadar arttırılır.
            _currentIndex += StepSize;
            if (IsContinue) // Eğer takip eden bir eleman var ise geri döndürülür
                return _books[_currentIndex];
            else
                return null;
        }

        // Takip eden ürün olup olmadığını belirten read-only özellik
        public bool IsContinue
        {
            get { return _currentIndex < _books.ProductCount; }
        }

        // O anki elemanı döndüren read-only özellik
        public Product Current
        {
            get { return _books[_currentIndex]; }
        }

        #endregion
    }   

    class Program
    {
        static void Main(string[] args)
        {
            ProductCollection products = new ProductCollection();

            products[0]=new Product{ ProductId=1, Name="330 ml Seramik Bardak", ListPrice=12M};
            products[1] = new Product { ProductId = 2, Name = "1 Lt Cam Bardak", ListPrice = 12.5M };
            products[2] = new Product { ProductId = 3, Name = "50 cl Pet Şişe", ListPrice = 14.45M };

            // Iterator nesnesi products isimli koleksiyonu kullanmak üzere oluşturulur
            ProductIterator iterator = new ProductIterator(products);

            // Adım sayısı belirlenir
            iterator.StepSize = 1;

            // First ile ilk elemana konumlanılır.
            // Koşul olarak IsContinue değerine bakılır
            // İlerleme için MoveNext metodu kullanılır.
            for (
                Product product = iterator.First()
                    ; iterator.IsContinue
                    ; product = iterator.MoveNext()
                    )
            {
                Console.WriteLine(product.ToString());
            }
        }
    }
}

Örneğimizi çalıştırdığımızda aşağıdaki ekran görüntüsünde yer alan sonuçları elde ederiz.

Tabiki amacımız sadece kalıbın nasıl uygulandığını öğrenmek olduğundan, işimizi kolaylaştırması için aslında içeride generic bir List<T> koleksiyonundan yararlandık. Ama tabiki var olan koleksiyonlar ile ifade edilemeyecek bir nesne bütünü olduğunda(özel bir ağaç yapısı olabilir) daha farklı bir depolama modeli kullanmamız gerekebilir. DoFactory.com sitesinin istatistiklerine göre neredeyse kullanılmadığı görülmemiş bir tasarım deseni ile karşı karşıyayız aslında. Peki, aramızdan kaç geliştirici C#  veya Vb.Net tarafında bu deseni isteyerek ve bilinçli olarak kullandı.

Ne kadar ilginç değil mi? Kodlama sırasında bir koleksiyon üzerinde dolaşırken tek yapmamız gereken çoğunlukla bir döngüyü kullanmaktır(for, foreach, while vb...). Hatta basit bir LINQ sorgusu sonrası filtrelenmiş bir içeriği bile for, while gibi döngüler ile dolaşmamız söz konusudur. Ama hiç arka planda bu sorumluluğu alan bir Iterator nesnesi olduğunu ve bir kalıp uygulandığını düşünmeyiz. Şimdi bu moral bozukluğu ile aslında işleri nasıl kolaylaştırmış olduğumuza bir bakalım Wink Yukarıda geliştirdiğimiz örneğin benzeri ile devam ediyor olacağız. İşte IEnumerable arayüzü ve yield anahtar kelimesi...

class ProductList
    : IEnumerable<Product>
    {
        private List<Product> list = new List<Product>();

        public Product this[int index]
        {
            get { return list[index]; }
            set { list.Add(value); }
        }

        #region IEnumerable<Product> Members

        public IEnumerator<Product> GetEnumerator()
        {
            foreach (Product product in list)
            {
                yield return product;
            }
        }

        #endregion

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            throw new NotImplementedException();
        }

        #endregion
    }

Dikkat edileceği üzere .Net içerisinde yer alan ve nesnelere iterasyon öğreten IEnumerable<T> arayüzünü yield ile birlikte ele alarak, ProductList nesne örnekleri içerisinde dolaşılabilmesini sağlayacak geliştirmeyi kolayca yapmış olduk.(Tabi bir versiyon daha geriye gidebilir ve IEnumerator arayüzünü kullanaraktanda bu işlemleri gerçekleştirebiliriz, bunuda hatırlatayalım) Örnek kullanımı ise şu şekilde gerçekleştirebiliriz;

ProductList products2 = new ProductList();
products2[0] = new Product { ProductId = 1, Name = "330 ml Seramik Bardak", ListPrice = 12M };
products2[1] = new Product { ProductId = 2, Name = "1 Lt Cam Bardak", ListPrice = 12.5M };
products2[2] = new Product { ProductId = 3, Name = "50 cl Pet Şişe", ListPrice = 14.45M };

foreach (Product product in products2)
{
     Console.WriteLine(product.ToString());
}

Uzun uzun zaman önce, C# 2.0 ile birlikte gelen yenilikleri anlatırken yield anahtar kelimesinide içeriklere kattığımı gayet net hatırlıyorum. Aslında bu anahtar kelimenin, var olan Iterator deseninin uygulanmasını dahada kolaylaştırdığı gün gibi ortada. Bir başka deyişle Iterator deseninin aslında .Net içerisine gömülü olduğunu söyleyebiliriz. Tabi kalıbı bizzat uygulamamış olsakta aslında IL(Intermediate Language) tarafındaki kodlara bakıldığında Iterator tasarım kalıbının izlerini görmemiz mümkündür.

Böylece geldik bir tasarım kalıbının daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

IteratorPattern.rar (30,73 kb)

Tasarım Desenleri - Mediator

Salı, 28 Temmuz 2009 21:04 by bsenyurt

Merhaba Arkadaşlar,

Yandaki resimde Zurich hava alanına ait bir görüntü yer almaktadır. Hava alanının ne kadar karmaşık olduğu aşikardır. Laughing Aslında yazımıza konu olarak Londra' daki Heathrow hava alanını dahil edecektim. Nitekim uzun zaman önce Discovery Channel' da izlediğim bir belgeselde, bir iniş ve birde kalkış pistiyle bu kadar işlek bir havalimanının ne kadar ustalıkla yönetildiği anlatılıyordu. Ancak yaptığım araştırmalar sonrası dünyadaki en iyi hava alanları arasında olmadığını gördüm.(http://www.worldairportawards.com/). Her neyse.

Konumuz aslında kimin daha iyi olduğu değil ama tüm hava alanları için ortak olan bir sorun. İnip kalkan ve hatta aynı havasahasına giren uçakların koordine edilmesi. Hiç çok işlek hava alanlarında kontrol kulesi olmadığını hayal ettiniz mi? Sealed  Sanıyorumki aşağıdaki konuşmalar ile karşılaşabilirdik.

- AzonAir - 110 : Ben sağdaki piste inmek üzere alçalıyorum arkadaşlar.

- CargoL  TL 101 : Hayır hayır oraya ben inecektim.

- AzonAir - 110 : Eeee...Önce gelen kapar.

- Öz Hawai - 444 : Savulunnnn!!! Ben o pistten kalkış yapıyorum.

- CargoL TL 101 : Hangi pist, hangi pist? ...

- Cazırt cuzurt, kraşş bummm...

Abartamaya gerek yok tabiki ama bu anektodunda bir manası var. Bir kontrol kulesi temel olarak tüm iniş kalkışları düzenler ve bu işi yaparken yukarıdaki gibi, uçakların birbirleri ile konuşmasına gerek kalmaz. Bir başka deyişle birbirleriyle etkileşimde olan uçakların tüm iletişimi, kontrol kulesi içerisinde hesaplanır ve işletilir. Dahada açık bir ifade ile kontrol kulesi aslında Mediator nesnesinin kendisidir. Mediator??? Hımmm..

Pekala konuyu biraz daha örneklemeye çalışalım. Wink

Bu kez bir network ağındaki kullanıcıları ve grupları göz önüne alalım. Kullanıcıların(Users) birden fazla gruba dahil olması muhtemeldir. Benzer şekilde bir grupta kendi içerisinde birden fazla kullanıcı barındırabilir. Yani kullanıcı ve gruplar arasında çoğa çok(Many to many) ilişki söz konusudur. Bu aktörler aslında birer nesne(Object) olarak düşünüldüklerinde, birbirlerine sıkı sıkıya bağlı olmaları(Tghtly Coupling), yönetimlerini zorlaştırmakla kalmaz, ileride yapılacak olan genişletmelerin çok fazla nesneyi etkilemesinede neden olur. Dolayısıyla aralarındaki bağı zayıflaştırmak(Loose Coupling) gerekir. Bu noktada veritabanı tasarımı ile uğraşanlar için sorunu çözmek son derece kolaydır. Nitekim bir ara tablo yardımıyla çoğa çok ilişkinin tesisi kolayca sağlanabilir. Diğer yandan Nesne Yönelimli(Object Oriented) tarafta, kullanıcı ve gruplar arasındaki iletişimi, onlardan soyutlayarak kendi içerisinde yönetecek olan bir ara nesneye ihtiyaç vardır. Kim...Mediator.

Anlaşılacağı üzere konumuz Behavioral tasarım kalıplarından olan Mediator desenidir. Bu desenin kullanım amacındaki odak noktası, nesne kümelerinin birbirleriyle nasıl haberleşebileceğini soyutlayan bir ara nesnenin kullanılmasıdır. Yazılım dünyasında bu konuya ilişkin verilebilecek en güzel örneklerden biriside Chat uygulamalarıdır. Bir Chat uygulamasına dahil olan katılımcıların her birinin birbirleriyle iletişim kurarak konuşması, zaman içerisinde ağ yükünü arttıracak ve yönetilemez hale getirecektir. Üstelik performans açısından da son derece kötü bir yaklaşımdır. Bunun yerine katılımcılar arasındaki iletişimin yönetimini sağlayacak bir merkezin olması önerilir. Böylece, katılımcılar hiç bir şekilde birbirlerinin nesnelerine istemeden müdahele edemez veya karmaşık hesaplamalar, karar mekanizmaları ile karşı karşıya kalamazlar. Katılımcılar isteklerini Mediator nesneye iletirler ve sonuçlardan haberdar edilirler. Chat uygulaması göz önüne alındığında Mediator aslında mesajlaşma işlemini üstlenen sunucu uygulama olarak düşünülebilir.

Gelin UML şemamıza bakalım ve arkasından geliştireceğimiz basit bir örnek yardımıyla konuyu irdelemeye çalışalım.

Şemadanda görüldüğü üzere Colleague' den Mediator'a doğru ve Concrete Mediator' dan, Concrete Colleague nesnelerine doğru tek yönlü ilişkiler(Asscoiation) söz konusdur. Yani okun solunda yer alan nesneler, okun ucundaki nesneleri ve izin verilen üyelerini kullanmaktadır. UML şemamızda yer alan nesnelerin ne işe yaradıklarını daha kolay kavrayabilmek amacıyla bir örnek üzerinden ilerleyebiliz. İşte sınıf diagramı ve kodlarımız. 

using System;
using System.Collections.Generic;
using System.Threading;

namespace MediatorPattern
{
    // Mediator
    interface IAirportControl
    {
        void Register(Airline airLine);
        void SuggestWay(string fligthNumber, string way);
    }

    // Concrete Mediator
    class IstanbulControl
        :IAirportControl
    {
        // Concrete Colleague nesne örnekleri bu koleksiyonda depolanmaktadır.
        private Dictionary<string, Airline> _planes;

        public IstanbulControl()
        {
            _planes = new Dictionary<string, Airline>();
        }

        #region IAirportControl Members

        // Kontrol kulesine çevredeki uçakların kayıt olması için Register metodu kullanılır. Bu metod parametre olarak Colleague' den türeyen her hangibir Concrete Colleague nesne örnepğini alabilir.
        public void Register(Airline airLine)
        {
            if (!_planes.ContainsValue(airLine))
                _planes[airLine.FlightNumber] = airLine;

            // Hava yolu şirketine ait uçağın, kuleden yeni rota talep edebilmesi için, Concrete Colleague nesne örneğinin, Mediator referansının bildirilmesi gerekir.
            airLine.Airport = this;
        }

        // Concrete Colleague nesne örneklerinin yeni rota talep ederken kullandıkları metod. Bu metod o anki koşullar gereği sakladığı diğer uçakların konum bilgilerinden yararlanıp bir takım sonuçlara varmaktadır. Bu sayede n tane kombinasyonun, her bir uçak tarafından ele alınması yerine, tüm bu kombinasyonlar daha az sayıya indirgenerek Mediator içerisinde değerlendirilebilmektedir.
        public void SuggestWay(string fligthNumber, string way)
        {
            // TODO: Diğer uçakların konumlarına bakılarak flightNumber için yeni bir rota önerilir. Gerekirse diğer uçaklarada farklı rotalar önerilebilir.
           
            // Sembolik olarak yeni bir rota belirleniyor. Bilgilendirme rotayı talep eden Concrete Colleague nesne örneğinin GetWay metoduna yapılan çağrı ile gerçekleştiriliyor.
            Thread.Sleep(250);
            Random rnd = new Random();
            _planes[fligthNumber].GetWay(String.Format("{0}:{1}E;{2}:{3}W", rnd.Next(1, 100).ToString(), rnd.Next(1, 100).ToString(), rnd.Next(1, 100).ToString(), rnd.Next(1, 100).ToString()));
        }

        #endregion
    }

    // Colleague
    abstract class Airline
    {
        public IAirportControl Airport { get; set; }
        public string FlightNumber { get; set; }
        public string From { get; set; }

        // Mediator' den yani kuleden yeni bir rota talep ederken kullanılan metod.
        public void RequestNewWay(string myWay)
        {
            // Çağrı dikkat edileceği üzere Mediator tipine ait nesne referansına doğru yapılmaktadır. Peki bu referansı nerede verdik. Bknz Register metodu. :)
            Airport.SuggestWay(FlightNumber, myWay);
        }

        // Mediator tipinin, çağırıda bulunacağı GetWay metodu. Bu metodun parametre içeriği, kuleden(Concrete Mediator) üzerinden gelmektedir.
        public virtual void GetWay(string messageFromAirport)
        {
            Console.WriteLine("{0} rotasına yönelmemiz gerekmektedir.", messageFromAirport);
        }
    }

    // Concrete Colleague
    class OzHawaii
        :Airline
    {
        public override void GetWay(string messageFromAirport)
        {
            Console.WriteLine("Oz Hawaii, Uçuş {0} : ",FlightNumber);
            base.GetWay(messageFromAirport);
        }
    }

    // Concrete Colleague
    class ZorluYol
        : Airline
    {
        public override void GetWay(string messageFromAirport)
        {
            Console.WriteLine("ZorluYol, Uçuş {0} : ", FlightNumber);
            base.GetWay(messageFromAirport);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // Kule nesnesi örneklenir(Concrete Mediator)
            IstanbulControl istanbulKule = new IstanbulControl();

            // Kuleden hizmet alacak tüm uçakların kendisini kuleye bildirmesi gerekmektedir. Bu nedenle uçaklar örneklendikten sonra Concrete Mediator tipine Register metedo yardımıyla kayıt olurlar.
            OzHawaii oh101 = new OzHawaii { Airport = istanbulKule, FlightNumber = "oh101", From="Hawai" };
            istanbulKule.Register(oh101);
            OzHawaii oh132 = new OzHawaii { Airport = istanbulKule, FlightNumber = "oh132", From="Roma" };
            istanbulKule.Register(oh132);
            ZorluYol zy99 = new ZorluYol { Airport = istanbulKule, FlightNumber = "zy99", From = "Antarktika" };
            istanbulKule.Register(zy99);

            // Uçaklar yeni rotalarını talep ederler.
            zy99.RequestNewWay("34:43E;41:41W");

            oh101.RequestNewWay("34:43E;41:41W");
        }
    }
}

Her ne kadar bir chat uygulaması yapmış olmasakta(ki dofactory.com' da Chat uygulaması örneğini bulabilirsiniz.) örneğimizdeki temel amacımız konumuza giriş yaptığımız kontrol kulesi senaryosunu simule edebilmektir. İlk etapta şunu rahatlıkla itiraf edebiliriz ki, Mediator deseni aslında uygulanması zor kalıplardan birisidir. Undecided

Örneğimizde meslektaş(Colleague) nesnelerimiz belirli hava yollarını işaret etmektedir. Örnek olarak OzHawaii ve ZorluYol isimli sınıflar, Concrete Colleague sınıflarımızdır. Bu havayolu şirketlerine ait uçakların inişleri ve kalkışları için yeni rotaları bulmak amacıyla birbirleri ile konuşmaları yerine bu işi Mediator nesnesi içerisinde yer alan basit bir fonksiyon üstlenmektedir. Örneğe göre hava yolu şirketleri kuleden, yaklaşma halindeyeken veya kalkıştan önce, yeni rota talebinde bulunabilirler. Bunun için Colleague tipi olan AirLine içerisindeki RequestNewWay metodu kullanılır. Bu metod ise aslında, Concrete Mediator tipi içerisinde yer alan SuggestWay isimli bir foksiyonu çağırmaktadır.  Dikkat edileceği üzere bu metod içerisinde, Mediator nesne örneğine abone olan tüm hava yolu şirketi uçaklarının o anki rota, yükseklik ve diğer bilgilerinden yararlanılarak, parametre olarak gelen Concrete Colleague nesne örneğine bilgilendirme yapılmaktadır. Bu bilgilendirmenin yapılabilmesi için tahmin edileceği üzere, Concrete Mediator tipinin, Concrete Colleague tiplerine erişebiliyor olması gerekmektedir. Bu sayede, Concrete Colleague tipleri içerisindeki GetWay metodları, Mediator nesne örneği içerisinden çağrılabilir. Bu da zaten UML şemasında yer alan, Concrete Mediator' den, Concrete Colleague nesnelerine olan tek yönlü ilişkiyi(Association) açıklamaktadır. Çok doğal olarak Concrete Mediator tipinin, hangi nesne kümelerini değerlendireceğini bilmesi gerekmektedir. Bu amaçla örneğimizde generic bir Dictionary<T,K> koleksiyonundan yararlanılmaktadır. Peki, Concrete Colleague nesne örnekleri, Concrete Mediator nesne örneklerine nasıl bildirilecektir? İşte bu noktada Register isimli metod devreye girmektedir. Buda UML şemamızda, Colleague' den Medaitor' e doğru olan tek yönlü ilişkiyi(Association) açıklamaktadır. Nitekim Register metodu parametre olarak AirPort tipinden(Concrete Colleague) nesne örnekleri almaktadır. Örneğimizi çalıştırdığımızda aşağıdaki ekran görüntüsündekine benzer sonuçları elde ederiz.

Özet olarak herhangibir havayoluna ait bir uçak, İstanbul kulesine yaklaştığında kendisine yeni bir rota talep ederken diğer uçaklar ile haberleşmek ve onların konumlarına göre hesaplamalar yaparak bir yön tayin etmek zorunda değildir. Tüm uçaklar bir birlerinden ayrıştırılmış ve yönlerini belirlemek üzere kullanılması gereken algoritmalar Mediator tipi içerisine kapsüllenmiştir. Biraz karışık bir desen implemantasyonu olmasına rağmen faydalı olduğunu umuyorum. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

MediatorPattern.rar (24,85 kb)

Tasarım Desenleri - FlyWeight

Pazartesi, 27 Temmuz 2009 18:30 by bsenyurt

Merhaba Arkadaşlar,

Yandaki resimde yer alan minik boksör aslında hafif siklette mücadele etmektedir ve biraz sonra işleyeceğimiz FlyWeight tasarım kalıbı ile uzaktan yakında hiç bir alakası bulunmamaktadır Sealed Ancak işleyeceğimiz tasarım kalbına bu ismin verilmesininde bir sebebi vardır. Bakalım neymiş? 

Yapısal(Structural) tasarım kalıplarından olan FlyWeight, bellek tüketimini optimize etmek amacıyla kullanılan bir desendir. Aslında detayına inildiğinde son derece zekice tasarlanmış ve pek çok noktada karşımıza çıkabilecek havuz mantığını içeren bir kalıp olduğu anlaşılabilir. Burada önemli olan nokta, bellek tüketiminin çok fazla sayıda nesnenin bir arada ele alınması sırasında ortaya çıkmasıdır. Buna göre söz konusu nesnelerin ortak olan, paylaşılabilen içerikleri ve bunların dışında kendilerine has durumları olduğu takdirde, nesne üretimlerini sürekli tekrar ettirmektense basit bir havuz içerisinden tedarik ettirmek, uygulamanın harcadığı bellek alanlarının optimize edilmesi için yeterli olacaktır. Bu açıdan bakıldığında desenin, paylaşımlı nesneleri efektif olarak kullanabilmek üzerine odaklandığını söyleyebiliriz.

Aslında, FlyWeight tasarım kalıbını hangi kaynaktan araştırırsak araştıralım ilk etapta dikkat edilmeyen çok önemli bir özellik içermektedir. Her bir FlyWeight nesnesi temel olarak iki önemli veri kümesinden oluşur. Kaynaklarda çoğunlukla intrinsic olarak geçen durum-bağımsız(State-Independent) kısım parçalardan birisir. Bu kısımda, çalışma zamanındaki tüm FlyWeight nesneleri tarafından saklanan paylaşılmış alanlar yer almaktadır. Diğer parça ise durum-bağımlı(State-Dependent) olarak bilinen ve kaynaklarda çoğunlukla extrinsic olarak belirtilen kısımdır. Bu kümedeki veriler ise istemci tarafından saklanır, hesap edilir ve FlyWeight nesne örneğine, yine FlyWeight' in bir operasyonu yardımıyla aktarılırlar. Tabiki desenin kullanım amacını daha net bir şekilde kavrayabilmek için bazı örnek senaryolar üzerinden gitmeye çalışabiliriz.

Bu desen ile ilişkili en belirgin örnek kelime işlemcilerinde ortaya çıkmaktadır. Nesneye dayalı olarak geliştirilen bir kelime işlemcisinde her bir karakterin nesne olarak oluşturulduğunu düşünelim. Her bir karakterin döküman içerisinde çok fazla sayıda kullanılabileceği ortadadır. Dolayısıyla aynı ortak özelliklere sahip olan bir çok karakter nesne örneğinin yönetimi söz konusudur ve buda doğal olarak bellek üzerinde daha fazla yer harcanmasına neden olacaktır. Aynı şekilde, bu nesnelerin tekrardan oluşturulmalarının maliyetide doğrudan performansa yansıyacaktır. Oysaki bu karakter nesnelerinin pek çoğu için ortak olan bir takım veriler söz konusudur. Örneğin, karakterlerin boyutları, font tipleri, büyüklükleri vb...Bunların dışında kelime işlemci açısından önem arz eden ve karakter nesneleri tarafından ortak olarak düşünülemeyecek bir takım verilerde vardır. Söz gelimi karakterlerin lokasyonu aslında istemci tarafından(yani kelime işlemci uygulama) belirlenebilir. Dolayısıyla karakterleri temsil eden tipler aslında ortak özellikleri bir yerde toplanıp, örnekleride havuzlanarak hafifleştirilebilir. Hafifleştirmek aslında bu desene neden FlyWeight adının verildiğinide ortaya koymaktadır.

Biz örnek uygulamamızda benzer bir senaryoyu ele alıyor olacağız. Senaryomuzda bir oyun sahnesinde yer alan çok sayıda asker olduğunu düşünüyoruz. Örnek olarak er ve çavuşları göz önüne alacağız. Bunların çok sayıda olduğunu ve sürekli tekrar eden sayısız örneklerinin uygulama alanında değerlendirildiğini göz önüne alırsak, oyun sahnesine getirdikleri bellek yükünü hafifletmek amacıyla, söz konusu asker nesnelerini birer FlyWeight tip haline getirmeyi deneyeceğiz. Sonrasında ise bu askerlerin oyun sahnesindeki yüklerini dengeleyecek, bir başka deyişle havuzu oluşturup, istemciye sunacak bir fabrika tipi(FlyWeight Factory) tasarlayacağız. Elbette öncesinde FlyWeight deseninin genel UML şemasına bakmamızda yarar var.

 

UML şemamızda gördüğümüz üzere FlyWeightFactory nesnesi ile FlyWeight nesnesi arasında bir Aggregation söz konusudur. Bu son derece doğaldır nitekim fabrikamız, kendi içerisinde yer alan bir depolama alanı ile FlyWeight nesne örneklerini havuzlamakta ve istemcinin ihtiyacı olan FlyWeight nesne örneklerini bu havuzdan tedarik etmektedir. Bu noktada istemci(Client) ile, FlyWeight Factory ve Concrete FlyWeight nesneleri arasında tek yönlü bir Association söz konusudur. Yani, Client diğerlerinin nesne örnekleri ve içeriklerini kullanmaktadır. Concrete FlyWeight tipi, türeyenler için Intrinsic state verileri ile Extrinsic state verilerinin ele alındığı ortak operasyonu tanımlamaktadır. Interface veya abstract sınıf tipinden tasarlanabilir.

Artık örneğimizi geliştirmeye başlayabiliriz. İşte sınıf diagramımız;

 

Ve kodlarımız;

using System.Collections.Generic;
using System;

namespace FlyWeight
{
    enum SoldierType
    {
        Private,
        Sergeant
    }

    // FlyWeight Class
    abstract class Soldier
    {
        #region Intrinsic Fields

        // Bütün FlyWeight nesne örnekleri tarafından ortak olan ve paylaşılan veriler
        protected string UnitName;
        protected string Guns;
        protected string Health;

        #endregion

        #region Extrinsic Fields

        // İstemci tarafından değerlendirilip hesaplanan ve MoveTo operasyonua gönderilerek FlyWeight nesne örnekleri tarafından değerlendirilen veriler
        protected int X;
        protected int Y;

        #endregion

        public abstract void MoveTo(int x, int y);
    }

    // Concrete FlyWeight
    class Private
        : Soldier
    {
        public Private()
        {
            // Intrinsict değerler set edilir
            UnitName = "SWAT";
            Guns = "Machine Gun";
            Health = "Good";
        }
        public override void MoveTo(int x, int y)
        {
            // Extrinsic değerler set edilir ve bir işlem gerçekleştirilir
            X = x;
            Y = y;
            Console.WriteLine("Er ({0}:{1}) noktasına hareket etti", X, Y);
        }
    }

    // Concrete FlyWeight
    class Sergeant
        : Soldier
    {
        public Sergeant()
        {
            UnitName = "SWAT";
            Guns = "Sword";
            Health = "Good";
        }
        public override void MoveTo(int x, int y)
        {
            X = x;
            Y = y;
            Console.WriteLine("Çavuş ({0}:{1}) noktasına hareket etti",X,Y);
        }
    }

    // FlyWeight Factory
    class SoldierFactory
    {
        // Depolama alanı(Havuz).
        // Uygulama ortamında tekrar edecek olan FlyWeight nesne örnekleri depolama alanında basit birer Key ile ifade edilir
        private Dictionary<SoldierType, Soldier> _soldiers;

        public SoldierFactory()
        {
            _soldiers = new Dictionary<SoldierType, Soldier>();
        }

        public Soldier GetSoldier(SoldierType sType)
        {
            Soldier soldier = null;

            // Eğer depolama alanında, parametre olarak gelen Key ile eşleşen bir FlyWeight nesnesi var ise onu çek
            if (_soldiers.ContainsKey(sType))
                soldier = _soldiers[sType];
            else
            {
                // Yoksa Key tipine bakarak uygun FlyWeight nesne örneğini oluştur ve depolama alanına(havuz) ekle
                if (sType == SoldierType.Private)
                    soldier = new Private();
                else if (sType == SoldierType.Sergeant)
                    soldier = new Sergeant();
                _soldiers.Add(sType, soldier);
            }

            // Elde edilen FlyWeight nesnesini geri döndür
            return soldier;
        }
    }

    class Program
    {
        public static void Main()
        {
            // İstemci için örnek bir FlyWeight nesne örneği dizisi oluşturulur
            SoldierType[] soldiers = { SoldierType.Private, SoldierType.Private, SoldierType.Sergeant, SoldierType.Private, SoldierType.Sergeant };

            // FlyWeight Factory nesnesi örneklernir
            SoldierFactory factory = new SoldierFactory();

            // Extrinsic değerler set edilir
            int localtionX = 10;
            int locationY = 10;

            foreach (SoldierType soldier in soldiers)
            {               
                localtionX += 10;
                locationY += 5;
                // O anki Soldier tipi için MoveTo operasyonu çağırılmadan önce fabrika nesnesinden tedarik edilir
                Soldier sld = factory.GetSoldier(soldier);
                // FlyWeight nesnesi üzerinden talep edilen operasyon çağrısı gerçekleştirilir
                sld.MoveTo(localtionX, locationY);
            }
        }
    }
}

Dilerseniz örneğimizi kısaca incelemeye çalışalım.

FlyWeight haline getirilen Private ve Sergeant isimli sınıflarımız abstract Soldier sınıfından türemektedir. Soldier sınıfı bu desene göre FlyWeight tipi görevini üstlenmekte olup kendisinden türeyen Private ve Sergeant tipleri asıl FlyWeight tiplerinin(Concurrent FlyWeight) modelleridir. Soldier tipi içerisinde bir askerin ortak alanları tutulmaktadır. Bunlardan UnitName, Guns ve Health özellikleri aslında içsel durumu(Intrinsic State) ifade etmektedir. Bir başka deyişle tüm benzer askerler için ortak ve paylaşılan bilgiler olarak düşünülmektedir. Tabiki senaryo gereği. Diğer yandan bir askerin, oyun sahası üzerindeki lokasyonu X ve Y isimli alanlarda tutulmaktadır. Bu alanların değerleri istemci açısından önemlidir. Nitekim oyun sahasında aynı askerin birden fazla örneği olabilmesine rağmen lokasyonları çeşitlilik gösterebilir. Bu nedenle X ve Y alanları aslında bir askerin harici durumu(Extrinsic State) ile alakalıdır. Peki bu durum nasıl değerlendirilir?

Desenin uygulanış biçimi gereği Extrinsic State içeriği, FlyWeight nesne örnekleri içerisine bir operasyon yardımıyla aktarılır ve değerlendirilir. İstemci tarafından gerçekleştirilecek bu operasyon çağrısı, örnek senaryomuzda MoveTo isimli metod ile ifade edilmektedir. Desenin belkide en önemli aktörlerinden biriside SoldierFactory(FlyWeight Factory) isimli sınıftır. Bu sınıf içerisinde dikkat edileceği üzere askerlerin birer anahtar ile saklanabilmeleri ve bu sayede birden fazla sayıda olan aynı FlyWeight nesnesinin tek bir sembol ile ifade edilebilmeleri mümkündür. Bunun için basit bir Dictionary<T,K> koleksiyonundan yararlanılmaktadır. Bu koleksyion tam olarak, FlyWeight nesne havuzunun kendisidir. Peki istemci tarafının talep edeceği Soldier(FlyWeight) nesne örnekleri nasıl elde edilecektir. GetSoldier metodu içerisinde buna uygun bir kod yer almaktadır. Dikkat edileceği üzere daha önceden havuz içerisinde(Koleksiyon içi), metoda gelen parametre tipinden bir anahtar var ise bunun karşılığı olan nesne anında geri döndürülmektedir. Ancak aksi durumda, söz konusu anahtar(Key) için bir nesne örneklenmekte, koleksiyona(yani havuza) eklenmekte ve geri döndürülmetkedir. Böylece, eklenen bu FlyWeight nesnesinin aynısından tekrar talep edilirse havuzdan karşılanması sağlanmış olacaktır.

Main metodu aslında istemcinin mevzuyu ele aldığı yerdir. soldiers isimli dizi içerisinde SoldierType enum sabitinden pek çok değer tutulmaktadır. Dikkat edileceği üzere tekrar eden bir sürü değer vardır. 3 Private ve 2 Sergeant tipi tanımlanmıştır. SoldierFactory örneklendikten sonra ise tüm bu askerler için ortak bir operasyon gerçekleştirilmektedir. Her biri bulundukları lokasyonlardan farklı bir yere doğru hareket ettirilmektedir. Hareket edilecek yeri istemci belirlemekte ve bunu FlyWeight nesnelerine MoveTo operasyonu yardımıyla bildirmektedir. Bu noktada for döngüsü içerisinde MoveTo operasyonundan önce(Extrinsic State değerlerinin ele alındığı fonksiyondan önce) fabrika nesnesinden bir Soldier talep edildiğine dikkat edilmelidir. İşte bellek tüketiminin kontrol altına alınmaya başaldığı yer burasıdır. Eğer havuzda bir FlyWeight nesne var ise oradan tedarik edilecek yoksa havuza eklendikten sonra geriye döndürülecektir ki bir sonraki karşılaşmada havuzdan tedarik edilebilsin. Mutlaka farketmişsinizdir bu desen Factory ve Singleton desenelerinide kullanmaktadır. Hatta State ve Strategy nesnelerininde bu kalıp içerisinde ele alındığı görülmektedir.

Bu desen ile ilişkili görsel dersi hazırlayana dek size tavsiyem, istemci tarafındaki for döngüsünde adım adım debug ederek ilerlemeniz olacaktır. Bununla birlikte mutlaka oodesing.com, dofactory.com ve sourcemaking.com/design_patterns sitelerindeki örnekleri incelemenizi öneririm. Böylece geldik bir desenin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

FlyWeightPattern.rar (23,15 kb)

Tasarım Desenleri - Chain of Responsibility

Cuma, 24 Temmuz 2009 23:15 by bsenyurt

Merhaba Arkadaşlar,

Dün gece çok garip bir rüya gördüm. Rüyamda denize açılmak için limanda duran tekneme doğru ilerliyordum. Derken kendimi kaptan köşkünde buldum. E tabi rüya bu. Hareket etmek istedim ama bir türlü beceremedim. Sonunda sorunun ne olduğunu bulmaya çalıştım ve yandaki manzaranın bir benzeri ile karşılaştım. Geminin demir halat zinciri(zincirleri) arap saçına dönmüştü. Sabah uyandığımda ilginç bir şekilde rüyayı hatırlayabildiğimi de farkettim.

 

Acaba bu bir işaret miydi? Evet sanırım bu davranışsal tasarım desenlerinden olan Chain of Responsibility' yi anlatmam için bir işaretti. Laughing İşte maceramız başlıyor.

Davranışsal(Behavioral) kalıplardan olan Chain of Responsibility deseni, ortak bir mesaj veya talebin(Request), birbirlerine zayıf bir şekilde bağlanmış(Loosly Coupled) nesneler arasında gezdirilmesi ve bu zincir içerisinde asıl sorumlu olanı tarafından ele alınması gerektiği vakalarda kullanılmaktadır. DoFactory.com güncel istatistiklerine baktığımızda kullanım oranı %40' lar seviyesinde görünsede, yazılışı son derece basit bir desendir. Desende mesajı(talebi) işleyecek olan asıl nesne örnekleri hayali bir zincir şeklinde dizilmektedir. İstemci, işlenmesini istediği bilgiyi bu zincirin en başında yer alan nesneye gönderir.  Zincir içerisinde yer alan nesne örnekleride söz konusu içeriği asıl işleneceği yere kadar göndererirler. Bir başka deyişle bir akıştan(Flow) söz etmemiz mümkündür. Zincire atılan her mesaj, zincire dahil olan tüm nesneler tarafından ele alınabilir veya bir sonrakine gönderilebilir.

Araştırma yaptığım pek çok kaynakta akılda kalıcı bir örnek olarak otomatik ürün makinelerine ait jeton slotları verilmektedir. Her tip jeton için bir slot oluşturmak aslında arka arkaya if blokları yazarak, gelen talebin anlaşılmaya çalışılmasına benzetilebilir. Bunun yerine makine üzerinde, her bir jetonu ele alan tek bir slot tasarlanır(Handler). Ürünü satın almak isteyen kişinin attığı jeton, verdiği komuta(Cola, çikolata vs istemek gibi) ve jetonun tipine göre, içeride uygun olan saklama alanına(ConcreteHandler) düşecektir. Sonrasında ise süreç, jetonun uygun olan saklama alanında değerlendirilerek istenilen ürünün teslim edilmesiyle tamamlanacaktır.

Yine gerçek hayat örneklerinden devam edersek; bir satın alma sürecinde, ödeme onayının kim tarafından verileceğinde de bu desen göz önüne alınabilir. Bu senaryoda ödeme talimatını onaylayabilecek olan yetkililer bulunur. Ancak gelen ödeme talebinin tutarına göre ilk yetkili personel, talebi bir üst yetkiliye iletmek zorunda olabilir. Bu durumda yetkililerin bir sorumluluk zincirinin parçası oldukları düşünülebilir. Burada en alt yetkiliye gelen ödeme talebi, gerektiğinde zincirin sonunda yer alan en üst yetkiliye kadar gidebilmelidir. Ayrıca bu yetkililerin her biri, birbirlerine sadece bu ödeme talepleri kapsamında bağlı olarak düşünülebilir. Bir başka deyişle ödeme onayı için her biri kendi sorumluluklarına sahip iken, farklı işlerde birbirlerinden tamamen bağımsızlardır.

Peki ya bizim dünyamızda(yani Matrix' in içerisinde) ne gibi örnekler verebiliriz? Belkide en yakın örnek olay güdümlü programlamada(Event Based Programming) görülür. Bazı senaryolarda bir olayın birden fazla nesne tarafından ele alınması gerektiği durumlar söz konusu olabilir. Bunu daha çok iç içe bileşenler içeren Form' larda veya diğer taşıyıcı(Container) kontrollerde görebiliriz. Nitekim hepsi için ortak sayılabilecek bir takım olaylar mevcuttur ve kullanıcının herhangibirini tetiklemesi halinde, bu kontrol zinciri içerisindeki hangi bileşenin üretilen olayla ilişkili olduğunun tespit edilmesi ve buna göre işlemlerinin yapılması gerekir. Chain of Responsibility deseni bu noktada devreye girerek üretilen olayın asıl sorumlusu olan bileşen tarafından ele alınmasında önemli bir rol oynamaktadır. Örnekler çoğaltılabilir. Söz gelimi, wikipedia da bu tasarım kalıbı ile ilişkili olaraktan, Loglama örneği verilmektedir.

Gelelim desenimizin UML şemasına;

Şekildende görüleceği üzere son derece basit bir tasarım kalıbı. Dikkat çekici ilk nokta, Handler tipi ile ConcreteHandler' lar arasında aggregation tadında bir ilişki olmasıdır. Aktörlerimiz ise;

Handler : Kendisinden türeyen ConcreteHandler' ların, talebi ele alması için gerekli arayüzü tanımlar. Abstract class veya Interface olarak tasarlanır.

ConcreteHandler :  Sorumlu olduğu talebi değerlendirir ve işler. Gerekirse talebi zincir içerisinde arkasından gelen nesneye iletir. Sonraki nesnenin ne olacağı genellikle istemci tarafında belirlenir.

Client : Talebi veya mesajı gönderir.

Artık kendi örneğimizi geliştirmemizin vakti geldi sanırım. Wink Örnek senaryomuzda sorumluluk zincirine dahil edeceğimiz bir servis bilgisi olacak. Servis bilgisini basit bir sınıf olarak tasarlayacağız. Servisin en önemli noktası lokasyon özelliğidir(Location). Servisin yerel makineden, bilgisayarın içinde bulunduğu bir network' ten veya internet üzerinden erişilebilir bir yerde olup olmama durumuna göre zincir içerisindeki sorumlu nesne tarafından ele alınmasını sağlamaya çalışacağız. İşte örnek kodlarımız ve sınıf çizelgemiz.

using System;

namespace ChainOfResponsibilityPattern
{
    // Yardımcı enum sabiti
    enum ServiceLocation
    {
        LocalMachine,
        Intranet,
        Internet,
        SecureZone,
    }

    // Zincir içerisindeki nesnelerde dolaşabilecek olan tip
    class ServiceInfo
    {
        public string Name { get; set; }
        public ServiceLocation Location { get; set; }
    }

    // Handler
    abstract class ServiceHandler
    {
        protected ServiceHandler _successor;
        public ServiceHandler Successor
        {
            set
            {
                _successor = value;
            }
        }

        public abstract void ProcessRequest(ServiceInfo sInfo);
    }

    // ConcreteHandler
    // Servisin Internet üzerinde olduğu durumu ele alır.
    // Sorumluluk zincirinin son sırasındaki tip
    class InternetHandler
        : ServiceHandler
    {
        public override void ProcessRequest(ServiceInfo sInfo)
        {
            // Eğer lokasyon Internet ise bu tipe ait nesnenin sorumluluğundadır Eğer Internet' de değilse artık sernin son halkası olduğundan gidecek başka bir yer kalmamıştır. Buna uygun şekilde bir hareket yapılmalıdır.
            if(sInfo.Location== ServiceLocation.Internet)
                Console.WriteLine("Web ortamı üzerinde yer alan bir servis.\n\t{0} için gerekli başlatma işlemleri yapılıyor.", sInfo.Name);
            else
                Console.WriteLine("Uzaydan gelen bir servis mi bu yauv?");
        }
    }

    // ConcreteHandler
    // Servisin Intranet üzerinde olduğu durumu ele alır.
    class IntranetHandler
        : ServiceHandler
    {
        public override void ProcessRequest(ServiceInfo sInfo)
        {
            // Eğer servis yerel makinede değilse zincirin bir sonraki tipi olan IntranetHandler' a gelir. Burada servis lokasyonunun Intranet olup olmadığına bakılır. Eğer öyleyse sorumluluk buradadır ve yerine getirilir.Ama değilse, zincirde bir sonraki tip olan InternetHandler nesne örneğine ait ProcessRequest metodu çağırılır.
            if(sInfo.Location== ServiceLocation.Intranet)
                Console.WriteLine("Şirket Network' ü üzerinde yer alan bir servis.\n\t{0} için gerekli başlatma işlemleri yapılıyor.", sInfo.Name);
            else if(_successor!=null)
                _successor.ProcessRequest(sInfo);
        }
    }

    // ConcreteHandler
    // Servisin yerel makineye ait olma durumunu ele alır.
    class LocalMachineHandler
        : ServiceHandler
    {
        public override void ProcessRequest(ServiceInfo sInfo)
        {
            // Eğer servis yerel makinede ise sorumluluk LocalMachineHandler nesne örneğine aittir. Ancak değilse, zincirde bir sonraki tip olan IntranetHandler' a ait ProcessRequest metodu çağırılır.
            if(sInfo.Location== ServiceLocation.LocalMachine)
                Console.WriteLine("Yerel makinede yer alan bir servis.\n\t{0} için gerekli başlatma işlemleri yapılıyor.", sInfo.Name);
            else if (_successor != null)
                _successor.ProcessRequest(sInfo);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // Önce zincire dahil olacak nesne örnekleri oluşturulur
            ServiceHandler handlerLocal = new LocalMachineHandler();
            ServiceHandler handlerIntranet = new IntranetHandler();
                ServiceHandler handlerInternet = new InternetHandler();

            // Zincirde yer alan her bir nesne kendisinden sonra gelecek olan nesneyi belirler.
            // Bu belirleme işlemi için Successor özelliği kullanılır.
            handlerLocal.Successor = handlerIntranet;
            handlerIntranet.Successor = handlerInternet;

            // Zincir halkasındaki nesneler tarafından kullanılacak olan nesne örneği oluşturulur.
            ServiceInfo info = new ServiceInfo { Name = "Order Process Service", Location = ServiceLocation.Intranet };

            // Zincirin ilk halkasındaki nesneye, talep gönderilir.
            handlerLocal.ProcessRequest(info);

            // Servisi kırdığımız nokta. Minik bir bomba ve antrenman sorusu.
            // handlerInternet.ProcessRequest(info);
        }
    }
}

Örneği çalıştırdığımızda aşağıdaki sonucu elde ederiz.

Servis lokasyonu intranet olduğundan, zincirin ilk halkasındaki handlerLocal isimli nesne örneği, sorumluluğu bir sonraki nesneye atmıştır. Bu nedenle IntranetHandler tipi içerisinde ProcessRequest metodu çalışmıştır ve sonraki adımda yer alan InternetHandler tipine bir geçiş söz konusu olmamıştır. info değişkenine ait Location özelliğinin değerini değiştirerek farklı sonuçları değerlendirebilirsiniz. Tabi mutlaka dikkatinizi çekmiştir, Location özelliğinin işaret ettiği ServiceLocation enum tipi içerisinde, zincir üzerinde ele alınmayan sabit bir değerde vardır. SecureZone. Dın dın dın dııınnnnn Sealed Sizce neden panik oldum acaba. Bunu bir düşünün.

Örnekte görüldüğü üzere, ServiceInfo tipinden bir nesne örneğinin Location özelliğinin değerine göre bir akış gerçekleştirilmektedir. Bu akışa ait zincir halkasının ilk nesnesi LocalMachineHandler iken son nesneside InternetHandler tipine aittir. Zincirdeki tüm tipler, ServiceHandler isimli abstract sınıftan türemektedir. Bu abstract sınıf, kendi tipinden bir özelliğe sahiptir. Successor isimli bu özellik ile amaç, halkadaki bir nesnenin kendisinden sonra gelecek olanı işaret etmesini sağlamaktır. Zincirdeki her nesnenin(Sonuncu hariç) bir Successor'u olmalıdır. Doğal olarak ilerleyen zamanlarda zincire başka bir nesnenin eklenmesi söz konusu olabilir. Bu nedenle, Successor özelliğinin aslında tüm ConcreteHandler' ların türediği ata tipi(Handler) kullanması son derece mantıklıdır.

İstemci tarafındaki kod içinde de dikkat edilmesi gereken bir takım hususlar vardır. Zincir içerisindeki her bir nesne örneklendikten sonra, sıraya göre birbirlerine Successor özellikleri üzerinden bağlanırlar. Bu doğal olarak zincirin doğru biçimde sıralanmasını gerektirir. Aksi durumda iş mantığına uygun olmayan sonuçlar alabiliriz. Öyleki asıl gidilmesi gereken yer yerine farklı bir yere gidilebilir.(Ödemenin onayını Genel Müdürün vermesi gerekirken, zincirdeki hatalı atama sonrası gişe memurunun trilyonlar için yetki vermesi gibi Undecided ) Yada zinciri kıracak şekilde bir çağrıda gelebilir. Örneğin kodun son kısmında minik bir bomba yer almaktadır. Buradaki sorunun ne olabileceğini bulmak ve bir yorum yapmak sizin göreviniz. Wink Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

ChainOfResponsibilityPattern.rar (25,32 kb)

Tasarım Desenleri - Decorator

Çarşamba, 22 Temmuz 2009 17:44 by bsenyurt

Merhaba Arkadaşlar,

Görsel tasarım işinden pek anladığımı söyleyemem. Embarassed Hatta ne zaman büyük bir hevesle Win Forms yada Asp.Net ön yüzü tasarlamaya kalksam renkleri bir türlü tutturamayak başladığım süreci hep yarım bırakmak zorunda kalırım. Bu sebepten genellikle arka plandaki iş mantıkları ile uğraşmayı tercih ederim. Sanırım WCF tarafında geliştirme yapmayı sevmemin en büyük nedenide bu olsa gerek. Anlaşılacağı üzere sanatsal yeteneğim pek yok. Hatta evimizin tüm dekorasyonu sevgili eşime aittir. Ama Decoration tasarım deseni deyince sanıyorumki anlatabilecek, paylaşabilecek bir kaç bilgim olabilir. İşte bu günkü konumuz Structural desenlerden olan Decorator Tasarım kalıbı.

Bu tasarım kalıbı bir nesneye dinamik olarak yeni sorumlulukların eklenmesi ve hatta var olanların çıkartılması amacıyla kullanılır. Bir açıdan bakıldığında nesneyi kendisinden türeyen alt sınıflar ile genişletmek yerine kullanılabilen alternatif bir yaklaşım olarak düşünülebilir. Desenin başlıca kahramanları ve UML şeması ise aşağıda görüldüğü gibidir.

Gelelim UML şemasında görülen kahramanlarımıza;

  • Component : Dinamik olarak sorumluklar eklenebilecek olan asıl nesne için sunulan arayüzdür. Interface veya abstract sınıf olarak tasarlanabilir.
  • ConcreteComponent : Sorumlulukların dinamik olarak eklenebilecekleri asıl bileşen sınıflarıdır. Component arayüzünü uyarlarlar ve abstract sınıf olarak tasarlanırlar.
  • Decorator : Decorator tipi hem Component arayüzünü uygular hemde kendi içerisinde Component tipinden bir nesne örneği referansını barındırır. Bu sebepten UML şemasındanda görüldüğü gibi Decorator ve Component arasında bir Aggregation ilişkisi mevcuttur.
  • ConcreteDecorator : Bileşenlere yeni sorumlulukları eklemekle görevli tiptir. Ek işlevler bu tip içerisinde tanımlanan üyelerdir.

Peki bu tasarım kalıbını hangi koşullarda kullanabilir yada tercih edebiliriz?

Aslında tanımı son derece açık olmasına rağmen zihnimizde daha iyi canlanabilmesi için bir kaç senaryoyu göz önüne almamızda yarar olduğu kanısındayım. Örneğin grafiksel bir arayüzün genişletilmesini göz önüne alalım. Normal bir Windows Form' unun kendisini, Scroll ile kullanılabilir şekilde genişletmek istediğimiz bir durumda Decorator desenini ele alabiliriz. Özellikle GUI(GraphicalUserInferface) Tookit' lerinin çoğu, Decorator desenini ele alarak genişletilmeye imkan sağlarlar. (Hatta O'Reilly yayınevinden çıkan C# 3.0 Design Patterns kitabında, Windows Form' larının genişletilmesine ilişkin bir Decorator örneği verilmektedir.)

Yakın dostumuz .Net Framework içerisinde yer alan Streaming alt yapısındada(Stream, FileStream, MemoryStream...) Decorator deseninin kullanıldığını görebiliriz. Kaynak olarak MSDN Magazine dergisinde(uzun bir süre abone olup posta hizmetinin eve sürekli geç getirmesi ve sonundada getirmemesi nedeniyle, online takip ettiğim ama her yazılımcının mutlaka takip etmesi gerektiğini düşündüğüm dergi) yayınlanan makaleyi okumanızı şiddetle öneririm. Yine .Net Framework içerisine baktığımızda, Asp.Net tarafında yer alan IHttpModule türevli sınıf zincirinde de Decorator kalıbının kullanıldığını görebiliriz. Örnekleri çoğaltmak mümkündür. Ancak gelin kendi örneğimizi yaparak desenin nasıl uygulandığını öğrenmeye çalışalım.

Uzun bir süredir oyunlar içerisinde kullanılan aktörlerden kendimi kurtarabilmiş değilim. Hal böyle olunca, örnek olarak bir ordunun sahip olduğu silahlar ve bu silahlara dinamik olarak yeni sorumluluklar ekleyebilmek için Decorator tasarım kalıbını ele alacağımız bir örnek geliştirmeye çalışıyor olacağız. Yandaki resimdende görüldüğü üzere senaryomuzun kahramanı bir Topçu bataryası. Ama tabiki bir Tank veya Uçak' ta olabilir. Bunların her birini Arms isimli bir bileşen olarak düşüneceğiz.

Örnek Console uygulamamızın kod içeriği aşağıdaki gibidir.

using System;
using System.Collections.Generic;

namespace Decorator
{
    // Component
    abstract class Arms
    {
        public string Name;
        public abstract void Fire();
    }

    // ConcreteComponent
    class Artillery
        : Arms
    {
        protected double _barrel;
        protected double _range;

        public Artillery(double barrel, double range, string name)
        {
            _barrel = barrel;
            _range = range;
            Name = name;
        }

        public override void Fire()
        {
            Console.WriteLine("{0} sınıfından olan topçu, {1} mm namlusundan {2} mesafeye ateşleme yaptı", Name, _barrel.ToString(), _range.ToString());
        }
    }

    // Decorator
    abstract class ArmsDecorator
        : Arms
    {
        protected Arms _arms;
        public ArmsDecorator(Arms arms)
        {
            _arms = arms;
        }
        public override void Fire()
        {
            if (_arms != null)
                _arms.Fire();
        }
    }

    // ConcreteDecorator
    class ArtilleryDecorator
        : ArmsDecorator
    {
        public ArtilleryDecorator(Arms arms)
            : base(arms)
        {
        }

        public void Defense()
        {
            Console.WriteLine("\t{0} Savunma Modu!", base._arms.Name);
        }
        public void Easy()
        {
            Console.WriteLine("\t{0} Atış serbest modu!", _arms.Name);
        }
        public override void Fire()
        {
            base.Fire();
        }
    }

    // Client
    class Program
    {
        static void Main()
        {
            // Bileşen örneklenir
            Artillery azman = new Artillery(125, 40, "Fırtına A1");
            azman.Fire();

            // Decorator nesnesi örneklenir
            ArtilleryDecorator  azmanDekorator= new ArtilleryDecorator(azman);
            // Decorator nesnesi üzerinden o anki asıl Component için(Artillery sınıfı) ek fonksiyonellikler çağırılır.
            azmanDekorator.Defense();
            azmanDekorator.Fire();
            azmanDekorator.Easy();
            azmanDekorator.Fire();
        }
    }
}

Örneğimizde Artillery isimli bir topçu bileşenine Defense ve Easy isimli yeni fonksiyonelliklerin eklenebilmesi için Decorator tasarım kalıbından yararlanılmıştır. Arms(Component) isimli bileşenden türeyen her gerçek tip için sisteme yeni bir Decorator eklenebilir. Bir başka deyişle, Tank isimli bir asıl bileşen sisteme dahil olduğunda pekala bunun içinde bir ConcreteDecorator tip(örneğin TankDecorator) söz konusu olabilir ki buda Tank için ek sorumlulukların dinamik olarak yüklenmesini sağlayabilir.

Tabiki bu desende kritik olan noktalardan biriside Decorator tipinin tanımlanış şeklidir. Bu tip kendi içerisinde, asıl bileşenleri kullanabilmek için Component tipinden bir üyeye sahiptir. Aynı zamanda bu üyenin initialize(başlatılma) işlemindende sorumludur. Diğer taraftan Component tipinden türediği için aslında Component içinde tanımlı olup, ConcreteComponent tipleri tarafından ezilmesi gereken kuralları kendiside uygulamak zorundadır. İşte bu noktada, kendi içerisinde sakladığı Component bileşeninin abstract üyelerini çağırarak çalışma zamanında taşıdığı asıl bileşenin ezdiği üyeleri devreye sokabilir(ArmsDecorator içinde overrride edilmiş Fire metoduna dikkat edelim). Ama puzzle' ın eksik kalan kısmını Decorator(ArmsDecorator) tipinden türeyen bileşenler üstlenmektedir. Örnekte yer alan ArtilleryDecorator(ConcreteDecorator), ArmsDecorator(Decorator)' den türemekte ve kendisine çalışma zamanında verilen asıl bileşeni üst sınıfın yapıcısına iletmektedir. Bu sebepten üst sınıf üzerinden çağırılan ve asıl bileşenler tarafından ezilen tüm üyelere ulaşabilir(ArtilleryDecorator içerisinde ezilmiş olan Fire metoduna dikkat edelim). Ama aynı zamanda kendisi içerisindede ek fonksiyonellikleri tanımlayabilir. Nitekim çalışma zamanında ConcreteDecorator(ArtilleryDecorator) tipinin çalıştığı nesne örneği, ek sorumluluklar üstlenmesi istenen asıl bileşen ConcreteComponent(Artillery)' den başkası değildir. Uygulamanın çalışma zamanı çıktısına baktığımızda aşağıdaki sonuçlar elde ettiğimizi görebiliriz.

Ancak bu desendede bazı eksik noktalar olabilir. Özellikle, nesnelere yeni fonksiyonelliklerin çalışma zamanında eklenmesi nedeni ile sistemin fonksiyonelliğine ait hataları ayıklamak(debug) daha zordur. Decorator deseni, Adapter ve Composite kalıpları ile zaman zaman karıştırılabilir. Adapter deseni bir nesnenin arayüzünü değiştirirken, Decorator kalıbı sorumlulukları(Responsibilities) değiştirmektedir. Bununla birlikte decorator deseni sadece bir component ile ilgilendiğinden, Composite kalıbının çakma bir hali olarakta düşünülebilir Wink Ancak bununla birlikte Decorator kalıbının bileşene ek sorumlulukla yüklediğide bir gerçektir. Böylece geldik bir yazımızın daha sonuna. Umarım sizler için yararlı olmuştur. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Decorator.rar (22,98 kb)

Tasarım Desenleri - Builder

Cuma, 17 Temmuz 2009 22:44 by bsenyurt

Merhaba Arkadaşlar

Zaman içerisinde geliştirdiğimiz uygulamalar son derece karmaşık bir hal alırlar. Uygulamanın çapının ve ihtiyaçlarının artması bir kenara içerisinde yer alan en küçük parçaların bile kullanımları kompleksleşebilir. Bu küçük birimlerin karmaşıklaşmasına etken olarak gösterilebilecek konulardan biriside, nesne üretimleri için kullanılan sınıfların sayılarının artması ve inşa işlemlerinin kompleks olması olarak düşünülebilir. Hal böyle olunca bazı vakalar için standartlaşmış kalıpları kullanmanın genişleyebilirlik ve ölçeklenebilirlik açısından büyük yararı vardır.

Nesne üretimi söz konusu olduğunda, Creational isimli kategoride yer alan tasarım kalıpları göz önüne alınmaktadır. Bunlardan birisi olan Builder deseni, karmaşık yapıdaki nesnelerin oluşturulmasında, istemcinin sadece nesne tipini belirterek üretimi gerçekleştirebilmesini sağlamak için kullanılmaktadır.  Bu desende istemcinin kullanmak istediği gerçek ürünün birden fazla sunumunun olabileceği göz önüne alınır. Bu farklı sunumların üretimi ise Builder adı verilen nesnelerin sorumluluğu altındadır. Dolayısıyla Builder kalıbından yararlanılarak aslı ürünün farklı sunumlarının elde edilebilmesi için gerekli olan karmaşık üretim süreçleri, istemciden tamamen soyutlanabilir. Desenin önemli olan özelliklerinden biriside Abstract Factory tasarım kalıbı ile çok benzer yapıda olmasıdır. Ancak arada bazı farklılıklarda vardır. Herşeyden önce Abstract Factory kalıbına göre, fabrikanın metodları kendi nesnelerinin üretiminden doğrudan sorumludur. Builder deseninin başlıca kahramanları aşağıda sıralandığı gibidir.

  • Builder: Product nesnesinin oluşturulması için gerekli soyut arayüzü sunar.
  • ConcreteBuilder: Product nesnesini oluşturur. Product ile ilişkili temel özellikleride tesis eder ve Product' ın elde edilebilmesi için(istemci tarafından) gerekli arayüzü sunar.
  • Director: Builder arayüzünü kullanarak nesne örneklemesini yapar.
  • Product: Üretim sonucu ortaya çıkan nesneyi temsil eder. Dahili yapısı(örneğin temel özellikleri) ConcreteBuilder tarafından inşa edilir.

Bu kahramanlarımızın aralarındaki ilişkileri aşağıdaki diagram üzerindeki dizilimi ise aşağıdaki gibidir.

Builder tasarım kalıbının kullanım oranı doFactory.com sitesinin istatistiklerine göre %40' lar civarındadır. Bunun en büyük nedenlerinden biriside uygun senaryoların tespit edilmesinin zor olmasıdır. Yinede desenin kullanılabileceği bir kaç senaryo üzerinde konuşarak daha kolay anlaşılmasını sağlayabiliriz.

Söz gelimi dofactory.com tarafından verilen örneği göz önüne alalım. Bu örnekte motorsiklet, otomobil, scooter gibi ürünler söz konusudur. Tüm bu araçların istemci açısından kullanılabilir olması için üretim işleminde motorun(her ne kadar motorsiklette ve scooter' da kapı olmasada Embarassed 0 veya null değeri kullanılabilir) , kapıların, viteslerin vb parçalarında üretimi gerekmektedir.

Aslında bu ortak fonksiyonellikler bu ürünlerin hepsi için geçerlidir. Yani bu araçların kendisi bir Product olarak temsil edilebilirler. İstemci, sadece kullanmak istediği ürünün farklı bir sunumunu elde etmek isteyecektir. Bu tip bir senaryoda istemcinin asıl ürüne ulaşmak için ele alması gereken üretim aşamalarından uzaklaştırılarak sadece üretmek istediği ürüne ait tipi bildirmesi yeterli olmalıdır. Bu senaryoda araç(Vehicle) aslında üründür(Product). Motorsiklet veya araba ise araç tipleridir ve üretim işlemleri sonucu ortaya bir Vehicle çıkartırlar. Yani desendeki ConcreteBuilder tipleridir. Bu senaryo pekala bir oyun programı içerisindeki araçların üretimi aşamasında göz önüne alınabilir. 

 

Diğer bir senaryoda bir firmanın çalışanlarına yılın belirli dönemlerinde farklı tipte promosyon ürünleri gönderdiğini düşünebiliriz. Söz gelimi farklı çalışan profilleri için sunumu farklı olan promosyon ürünlerinin geliştirilmesi safhasındaki üretim karmaşıklığı, Builder deseni ile istemciden uzaklaştırılabilir. Bu senaryoda promosyonun kendisi ürün iken, promosyon ürününü kullanacak olan profil sahipleri ConcreteBuilder tipleri olarak düşünülebilir.

Peki daha gerçekçi bir örnek olamaz mı? Her zaman dediğim gibi, aslında tasarım kalıplarının çoğunun .Net Framework içerisinde kullanıldığını kolaylıkla görebiliriz. Builder tasarım kalıbı için düşünülebilecek en güzel örnek Connection String Builder operasyonudur.

Şekildende görüldüğü gibi bir istemcinin, kullanmak istediği Connection tipi için uygun olan bağlantı bilgisine ihtiyaç vardır. Burada bağlantının string şeklindeki içeriği önemlidir. Bu içeriğin sunum şekli ise OleDb, SQL, ODBC için farklıdır. Dolayısıyla söz konusu farklı string üretimleri için bazı ConcreteBuilder tiplerinden(OleDbConnectionStringBuilder vb...) yararlanılır. Her ne kadar DbConnectionStringBuilder abstract bir sınıf olmasada, Builder tipinin görevini üstlenmektedir.

Ancak benim popüler senaryom şu anda midemden beynime doğru gelen sinyallerinde söylediği üzere Pizzacı örneğidir.

 

Nitekim şu aşamada heleki hafta sonuna girdiğimiz şu güzel Cuma gecesinde, bu deseni eğlenceli bir şekilde ele almamamız için hiç bir sebep bulunmamaktadır. Wink İşte deseni ele aldığımız kod parçaları.

using System;

namespace Builder
{
    // Product class
    public class Pizza
    {
        public string PizzaTipi { get; set; }
        public string Hamur { get; set; }
        public string Sos { get; set; }

        public override string ToString()
        {
            return String.Format("{0} {1} {2}", PizzaTipi, Hamur, Sos);
        }
    }

    // Builder class
    public abstract class PizzaBuilder
    {
        protected Pizza _pizza;

        public Pizza Pizza
        {
            get { return _pizza; }
        }

        public abstract void SosuHazirla();
        public abstract void HamuruHazirla();
    }

    // ConcreteBuilder class
    public class BaharatliPizzaBuilder
        : PizzaBuilder
    {
        public BaharatliPizzaBuilder()
        {
            _pizza = new Pizza { PizzaTipi = "Baharatlı Baharatlı" };
        }
        public override void SosuHazirla()
        {
            _pizza.Sos = "Acı sos, pepperoni, atom biber";
        }

        public override void HamuruHazirla()
        {
            _pizza.Hamur = "İnce Kenar, Kaşarlı";
        }
    }

    // ConcreteBuilder Class
    public class DortMevsimPizzaBuilder
        : PizzaBuilder
    {
        public DortMevsimPizzaBuilder()
        {
            _pizza = new Pizza { PizzaTipi = "4 Mevsim" };
        }
        public override void SosuHazirla()
        {
            _pizza.Sos = "Biber, Domates, Peynir, Salam, Sosis";
        }

        public override void HamuruHazirla()
        {
            _pizza.Hamur = "Kalın, fesleğenli";
        }
    }

    // Director Class
    public class VedenikliKamil
    {
        public void Olustur(PizzaBuilder vBuilder)
        {
            vBuilder.SosuHazirla();
            vBuilder.HamuruHazirla();
        }
    }

    // Client class
    class Program
    {
        static void Main(string[] args)
        {
            PizzaBuilder vBuilder;

            VedenikliKamil kamil= new VedenikliKamil();
            vBuilder = new BaharatliPizzaBuilder();
            
            kamil.Olustur(vBuilder);
            Console.WriteLine(vBuilder.Pizza.ToString());

            vBuilder = new DortMevsimPizzaBuilder();
            kamil.Olustur(vBuilder);
            Console.WriteLine(vBuilder.Pizza.ToString());
        }
    }
}

İstemcinin tek derdi istediği tipte bir pizza almaktır. Örneğin 4 mevsim veya Baharatlı pizza. Bu pizzaların içinde ise soslarının ve hamurlarının belirlenerek üretim işlemine dahil edilmesi gerekmektedir. Bu VenedikliKamil açısından kolay olmakla birlikte istemciyi ilgilendiren bir durum değildir. Bu nedenle istemcinin sadece pizza üretimini gerçekleştiren asıl ConcreteBuilder nesne örneğini seçmesi yeterlidir. Bu seçim işlemi Director sınıfı içerisindeki Olustur metoduna parametre olarak gönderilir. Sonrasında ise istemcinin istediği pizza üretilerek elde edilir. Örneği çalıştırdığımızda aşağıdaki sonuçları elde ederiz.

 

Umarım sizler içinde faydalı bir anlatım olmuştur. Her zamanki gibi bu desenin görsel dersinide en kısa sürede eklemeye çalışacağım. Bu yazının üstünede şöyle güzel bir espresso içilir kanımca Wink 

Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Builder.rar (23,15 kb)

Tasarım Desenleri - Composite

Pazar, 12 Temmuz 2009 19:00 by bsenyurt

Merhaba Arkadaşlar,

Küçüklüğümde son derece şanslı bir çocuktum. Uzun yıllar Almanya' da çalışan rahmetli babam ve annemin pek çok arkadaşı bana Lego oyuncaklarından göndermiştir. Evde günümün büyük bir çoğunluğunu bu legolar ile oynarak geçirir ve okul zamanında derslerimden geri kalırdım Embarassed Lego oyuncakları zaman içerisinde öylesine geliştiki, artık efsane haline gelen pek çok filmin (Starwars, Indiana Jones vb...) konseptini içerdiğini görmeye başladık. Şimdi bunun konumuz ile ne alakası var diye düşünüyorum. Hemen aralarında rütbe ilişkisi olan legolardan oluşan bir orduyu gözümde canlandırıyorum. Generalden en alt kademedeki ere kadar kadar pek çok rütbe yer alıyor.

Aslında bu organizasyonda yer alan bireylerin hepsi birer asker. Rütbeleri farklı bile olsa. Önemli olan detaylardan birisi bunların aralarındaki organizasyonel ilişkinin aslında bir ağaç yapısı(Tree) şeklinde ifade edilebiliyor olması. Bu ağaç ilişkisinden yararlanarak alt ve üstler arasında bilgi dolaştırılmasıda mümkün. Peki ya bu askerler nesne yönelimli(Object Oriented) bir programlama ortamında ifade ediliyorlarsa, organizasyonun ağaç yapısını temsil edebilecek bir kalıp mümkün olabilir mi? Tabiki olabilir ve bu kalıbın adı Composite tasarım desenidir. Aslında bu desenin temel amacı, nesnelerin ağaç yapısına göre düzenelenebilmesidir. Desenin içerisinde yer alan kahramanlar ise aşağıdaki örnek UML diagramında görüldüğü gibidir.

Component tipi içerisinde kendisinden türeyen Composite ve Leaf tiplerinin ortaklaşa kullanacağı üyeler dışında, ezmeleri gereken kurallarda tanımlanmaktadır. Bu anlamda Component bileşenini abstract bir sınıf veya arayüz(Interface) olarak tanımlayabiliriz. Şemadanda görüleceği üzere, Component tipi içerisinde yine Component tipinden parametre alıp ekleme ve çıkarma işlevlerini üstlenen ve ezilen(Override) metodlar vardır. Ancak bu metodlar Leaf tipi içerisinde ezilmemiştir. Aslında Leaf tiplerini, kendi içerisinde başka bir Component tipi içermeyecek çalışma zamanı nesnelerini örneklediğini düşünebiliriz. Bunun aksine Composite tipi, kendi içerisinde Component tipinden oluşan bir koleksiyon içermektedir. Bir başka deyişle, altında Leaf tiplerini veya başka Composite tipleri içerebilecek bir nesne üretimi sağlanabilmektedir.

Desende dikkat edilmesi gereken noktalardan biriside, Component tipinden tanımlanan bazı operasyonların Composite tipte uygulanırken Leaf tipinde uygulanmamasıdır. Örneğe göre AddSoldier ve RemoveSoldier metodlarının Leaf tipi içerisinde bir anlamı yoktur. Nitekim Leaf kendi altında başka bir Component tipi içermeyecek nesne örneklerini temsil etmek üzere ele alınmalıdır. Diğer yandan Component tipi içerisinde tanımlanıp hem Composite hemde Leaf tipinde uygulanan ortak operasyonların, Composite içerisindeki uygulanış şeklide biraz farklıdır. Bu farka göre, Composite içerisindeki ortak operasyon(örneğimize göre ExecuteOrder metodu), Composite nesne örneğine bağlı tüm nesneleri kapsamalıdır. Sanırım kafamız iyice allak bullak oldu. Undecided

Bu nedenle olayı XML ağaçlarını düşünerektende ele alabiliriz. XML alt yapısını kod tarafında ifade ederken, Composite tasarım kalıbına göre bir ağacın OOP' ye uygun olacak şekilde tasarlanması kolay olabilir. XML yapısı gereği, herkes birer element(yada node) olarak ifade edilebilirken, aslında kendilerine bağlı başka alt elementleride barındırabilirler. Böylece çalışma zamanında birbirlerine bağlı dallar üzerinde oturan XML ağaçları kolaylıkla tasarlanabilir. Hatta bir Component üzerinden varsa alt veya üst Component tipinede ulaşılabilir. (Size tavsiyem bir XML verisinin içeriğindeki elementleri ifade edecek bir modeli, Composite desenine göre tasarlamaya çalışmanız olacaktır.) 

Ancak yapısal(Stuctural) desenelerden olan bu kalıpta dikkat edilmesi gereken en önemli nokta, ağaç içerisindeki tüm nesnelerin aslında aynı arayüzü(veya soyut tipi) uyguluyor olmasıdır. Bu nedenle nesne istemcileri, ağaçta yer alan Composite ve Leaf örneklerine aynı şekilde davranırlar.

Dilerseniz basit bir örnek üzerinden ilerleyelim. Örneğimizde, kurduğumuz ordunun içerisindeki organizasyonel ağacı tasarlamaya çalışıyor olacağız. Buna göre

General
   Colonel
      LieutenantColonel
         Major
            Captain
               Lieutenant

şeklinde bir organizasyonumuz olduğunu göz önüne alabiliriz.

Organizasyondaki herkes bir askerdir(Soldier) ki buda bizim Component tipimiz ile ifade edilmektedir. Composite tipimiz(CompositeSoldier) isterse kendi içerisinde birden fazla başka Component(PrimitiveSoldier veya CompositeSoldier olabilir) tiplerini içerebilmelidir. Tüm askerlerin, ister Leaf ister Composite olsun uygulayacağı birde ortak operasyonumuz vardır(ExcuteOrder). İşte sınıf diagramımız ve uygulama kodlarımız.

using System;
using System.Collections.Generic;

namespace CompositePattern
{
 /// <summary>
 /// Askerlerin rütbeleri
 /// </summary>
 enum Rank
 {
  General,
  Colonel,
  LieutenantColonel,
  Major,
  Captain,
  Lieutenant
 }
 
 /// <summary>
 /// Component sınıfı
 /// </summary>
 abstract class Soldier
 {
  protected string _name;
  protected Rank _rank;
  
  public Soldier(string name,Rank rank)
  {
   _name=name;
   _rank=rank;
  }
  
  public abstract void AddSoldier(Soldier soldier);
  public abstract void RemoveSoldier(Soldier soldier);  
  public abstract void ExecuteOrder(); // Hem Leaf hemde Composite tipi için uygulanacak olan fonksiyon
   
 }
 
 /// <summary>
 /// Leaf class
 /// </summary>
 class PrimitiveSoldier
  :Soldier{
  
  public PrimitiveSoldier(string name,Rank rank)
   :base(name,rank)
  {
   
  }
  // Bu fonksiyonun Leaf için anlamı yoktur.
  public override void AddSoldier(Soldier soldier)
  {
   throw new NotImplementedException();
  }
  // Bu fonksiyonun Leaf için anlamı yoktur.
  public override void RemoveSoldier(Soldier soldier)
  {
   throw new NotImplementedException();
  }
  
  public override void ExecuteOrder()
  {
   Console.WriteLine(String.Format("{0} {1}",_rank,_name));
  }  
 }
 
 /// <summary>
 /// Composite Class
 /// </summary>
 class CompositeSoldier
  :Soldier{
 
  // Composite tip kendi içerisinde birden fazla Component tipi içerebilir. Bu tipleri bir koleksiyon içerisinde tutabilir.
  private List<Soldier> _soldiers=new List<Soldier>();
  
  public CompositeSoldier(string name,Rank rank)
   :base(name,rank)
  {
   
  }
  
  // Composite tipin altına bir Component eklemek için kullanılır
  public override void AddSoldier(Soldier soldier)
  {
   _soldiers.Add(soldier);
  }
  // Composite tipin altındaki koleksiyon içerisinden bir Component tipinin çıkartmak için kullanılır
  public override void RemoveSoldier(Soldier soldier)
  {
   _soldiers.Remove(soldier);
  }  
  // Önemli nokta. Composite tip içerisindeki bu operasyon, Composite tipe bağlı tüm Component'ler için gerçekleştirilir.
  public override void ExecuteOrder()
  {
   Console.WriteLine(String.Format("{0} {1}",_rank,_name));
   foreach(Soldier soldier in _soldiers)
   {
    soldier.ExecuteOrder();
   }
  }
 }
 class Program
 {
  public static void Main(string[] args)
  {
   // Root oluşturulur.   
   CompositeSoldier generalBurak=new CompositeSoldier("Burak",Rank.General);
   
   // root altına Leaf tipten nesne örnekleri eklenir.
   generalBurak.AddSoldier(new PrimitiveSoldier("Mayk",Rank.Colonel));
   generalBurak.AddSoldier(new PrimitiveSoldier("Tobiassen",Rank.Colonel));
   
   // Composite tipler oluşturulur.
   CompositeSoldier colonelNevi=new CompositeSoldier("Nevi", Rank.Colonel);   
   CompositeSoldier lieutenantColonelZing=new CompositeSoldier("Zing", Rank.LieutenantColonel);
   
   // Composite tipe bağlı primitive tipler oluşturulur.
   lieutenantColonelZing.AddSoldier(new PrimitiveSoldier("Tomasson", Rank.Captain));
   colonelNevi.AddSoldier(lieutenantColonelZing);
   colonelNevi.AddSoldier(new PrimitiveSoldier("Mayro", Rank.LieutenantColonel));
   // Root' un altına Composite nesne örneği eklenir.
   generalBurak.AddSoldier(colonelNevi);
   
   //
   generalBurak.AddSoldier(new PrimitiveSoldier("Zulu",Rank.Colonel));
   
   // root için ExecuteOrder operasyonu uygulanır. Buna göre root altındaki tüm nesneler için bu operasyon uygulanır
   generalBurak.ExecuteOrder();
   
   Console.ReadLine();
  }
 }
}

Uygulamayı çalıştırdığımızda aşağıdaki sonucu alırız.

Görüldüğü gibi emir modeli general üzerinden uygulandığı için organizasyonda generale bağlı olan herkese iletilebilmektedir. Aslında son derece kolay ve kullanım alanı geniş olan bir deseni inceledik. Ancak Console uygulamasıda olsa eksik olan kısımlar var gibi. Söz gelimi hiyerarşiye göre askerlerin ekrana girintili olarak yazdırılması sağlanabilir. Wink Hatta bunu bir WPF veya Windows uygulamasında görsel olarak yapmaya çalışmanızı öneririm. Bu mukaddes görevleride sizlere bırakıyorum.

Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Not: Desene ait görsel anlatım en yakın zamanda eklenecektir.

CompositePattern.rar (13,44 kb)