Yazılım Mimarileri ve Tasarım Desenleri Üzerine

C# Composite Design Pattern(Composite Tasarım Deseni)

Merhaba,

Bu içeriğimizde Yapısal Tasarım Kalıplarından(Structural Patterns) olan Composite Tasarım Desenini(Composite Design Pattern) tam teferruatlı inceleyecek ve bol bol örnekler ile zihinlerimize mantıksal yapısını kazımaya çalışacağız. O halde fazla vakit kaybetmeksizin buyrun başlayalım…

Başlarken

Her design pattern makalesinde yaptığımız gibi öncelikle konuya dair bol teorik bilgi verdikten sonra bilgileri pratikselleştirmek için bir örnek üzerinden eş zamanlı anlatım sergileyeceğiz. Ardından Composite pattern’ın stratejik ve mantıksal yapısını daha da özümseyebilmek ve kullanılabileceği durumları hızlıca kavrayabilmek için beş adet örnek senaryo üzerinden pratik gerçekleştiriyor olacağız. Bu senaryoların her biri, çözümü ile birlikte makalemizin altındaki diğer sayfalarda sırasıyla sunuluyor olacaktır. Şimdiden iyi ve verimli okumalar dilerim…

Composite Design Pattern Nedir?

Bazen yazılım tasarımları süreçlerinde nesnelerin ağaç yapıları halinde oluşturulması ve ardından bu yapılarla tek tek nesnelermiş gibi çalışılması gerekebilir. İşte böyle durumlarda Composite pattern tasarımsal açıdan imdadınıza yetişebilir.

Composite pattern, kendi içlerinde birbirlerinden farklı olan bir grup nesnenin sanki tek bir bütün nesneymiş gibi kullanılmasını sağlamaktadır. Böylece farklı nesneleri bir ağaç yapısında birleştirip genel anlamda parça bütün ilişkisini yeniden düzenleyip şekillendirmektedir.

Composite pattern’a teoride şöyle bir örnek verebiliriz.
‘Kutu’ ve ‘Ürün’ olmak üzere iki türlü nesne olduğunu düşünelim. Kutu nesnesi, içerisinde birden fazla ürün barındırabileceği gibi ayriyeten kutu da barındırabilir. Yani bir kutu içerisinde bir başka kutu olabilir. Aynı şekilde kutu içerisindeki kutu da içerisinde ürün barındırabileceği gibi yine farklı bir kutu da barındırabilir. Haliyle bu silsile böyle devam edebilir.
Bu nesnelerin kullanıldığı bir sipariş sistemi oluşturduğumuzu tahayyül edelim ve yandaki görüntüdeki gibi bir sepeti düşünelim. Sepet içerisinde siparişler basit ürünlerden ibaret olabileceği gibi içi ürünlerle dolu kutulardan da ibaret olabilir. İşte böyle bir sipariş durumunda siz olsanız siparişin toplam fiyatını nasıl belirlerdiniz?

Muhtemelen çözüm olarak tüm kutuları tek tek açıp, mevcut tüm ürünleri gözden geçirdikten sonra toplam fiyatı hesaplardınız. Bu gerçek dünyada yapılabilitesi olan yegane yoldur diyebiliriz. Ancak yazılımsal olarak bu hesaplamayı yapabilmek pekte kolay değildir. Nihayetinde iç içe kaç kutu olduğunu bilemediğimiz için recursive bir yaklaşım sergilememiz gerekmektedir.

Composite pattern ise bu tarz ağaç yapısı senaryolarında gerçekleştirilecek bütünsel işlemleri yürütebilmek için her bir composite(kutu) nesnesi üzerinde operasyonu gerçekleştirmektedir. Yukarıdaki teorik örnekte olduğu gibi, toplam fiyatı hesaplayabilmek için her bir kutunun içerdiği ürünler üzerinden toplam fiyatı hesaplayarak öğrenecek ve bunu iç içe olan tüm kutular için gerçekleştirecektir.Bu yaklaşımın en büyük yararı, ağaç yapısını oluşturan alt kırılım sınıflarıyla ilgilenmemize gerek olmamasıdır. Yani bir nesnenin basit bir ürün mü yoksa içinde birden fazla ürün ve kutu barındıran karmaşık bir kutu mu olup olmadığını bilmemize gerek yoktur. Ortak bir arayüz aracılığıyla her iki nesnede aynı muameleyi görecektir. Bu arayüz sayesinde tetiklenen metot üzerinden nesneler bu tetiklenmeyi ağaçtan aşağı ileterek operasyonun yürütülmesini sağlayacaktır.

Gerçek Dünya Analojisi

Biliyorsunuz ki, çoğu ülkenin orduları hiyerarşik olarak yapılandırılmıştır. Bir ordu birbirleriyle ilişkili birkaç bölümden meydana gelmektedir. Misal olarak, bir tümen tugaylardan oluşurken, bir tugay alaylardan oluşmakta, alaylar ise mangalara kadar ayrılan müfrezelerden meydana gelmektedir. Haliyle bu şekilde ilişkilendirilmiş bir yapılanmada hiyerarşinin en üstünden verilen emirler, her seviyedeki tüm askerlere ulaştırılacak şekilde aktarılmaktadır. İşte bu hiyerarşinin düzenlenmesinde Composite pattern rahatlıkla kullanılabilir.

Benzer mantıkla çalıştığınız şirkette veya kurumda ekibin organizasyonel yapısını ve personellerin pozisyonlarını işlevsel ilişkiler üzerinden hiyerarşik olarak gösteren teşkilat şemaları mevcuttur. Bu teşkilat şemaları da tıpkı ordularda olduğu gibi hiyerarşinin en üstünden altına doğru sorumluluk ağlarını göstermektedir. İşlevsel açıdan sorumluluklar, en üstten aşağı doğru her seviyedeki personellere ayrıştırılacak şekilde tasarlanmaktadır. Buradaki mantıkta yazılımsal düzleme Composite pattern ile rahatlıkla aktarılabilir.

Composite Design Pattern’ın Stratejisi

Composite pattern’ın uygulanacağı ağaç yapıları misali tasarımlarda Component, Composite ve Leaf isimlerinde olmak üzere üç farklı sorumlulukta sınıf tasarlanması gerekmektedir.
Bu sınıfların ne olduğunu ve hangi sorumlulukları üstlendiğini açıklamamız gerekirse eğer;

Composite Object ile Leaf Object arasındaki ilişkiyi güzel bir şekilde temsil eden görsel şema…

Composite Pattern stratejisinin katılımcıları yukarıda görüldüğü gibidir… Burada dikkat edilmesi gereken nokta; basit öğeler Leaf, karmaşık öğeler ise Composite olarak nitelendirilmektedir…

Composite pattern’da birden çoğa hiyerarşik yapılanma söz konusudur.

Teknik Olarak Composite Design Pattern

Composite pattern’ı teknik olarak inceleyebilmek için yukarıdaki satırlarda değindiğimiz gerçek dünya analojisindeki asker senaryosundan yola çıkalım ve pratiksel açıdan ilgili senaryonun örneğini gerçekleştirerek seyredelim.

SENARYO
Emir komuta sistemine sahip ordu modelini yazılımsal olarak inşa edelim ve üst rütbelerden gelen emirleri tüm seviyelerdeki askerlere iletelim.
ÇÖZÜM
  • Adım 1 (Component Soyut Sınıfının Tasarlanması)
    Her şeyden önce ağaç yapılanmasında Leaf ve Composite nesnelerimizin temelini teşkil edecek olan Component soyut sınıfını tasarlayarak başlayalım.
        //Component
        public abstract class Soldier
        {
            protected string _name;
            protected Rank _rank;
    
            protected 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();
        }
    

    Yukarıdaki kod bloğunda bir asker’i ifade eden Component soyut sınıf tasarımı gerçekleştirilmiştir. Dikkat ederseniz askere dair isim(_name) ve rütbe(_rank) bilgileri constructor üzerinden edinilmiş ve sadece Composite nesnelerde kullanılmak üzere ‘AddSoldier’ ve ‘RemoveSoldier’ abstract fonksiyonlarıyla birlikte hem Composite hem de Leaf nesnelerde kullanılacak ‘ExecuteOrder’ abstract metodu tanımlanmıştır.

    Esasında, Composite nesneler için tanımlanmış olan fonksiyonlar Leaf nesnelere de override edilecek olsa dahi ilgili nesneler için anlamlı fonksiyonlar olmayacak, boş olarak kalacaktırlar. Haliyle yazımızın ileriki satırlarında bu durumun Interface Segregation Principle‘a aykırılık oluşturabileceğini görecek ve nasıl önlemler alabileceğimizin istişaresini ediyor olacağız. Ayrıca sonraki sayfalarda sunacağımız beş adet örnek senaryo üzerinden de bu riski ortadan kaldıracak olan yaklaşımı sürekli tatbik ederek, tecrübe etmiş olacağız.

    Bu arada yukarıdaki kodda bulunan rütbe(Rank) içeriği aşağıdaki gibi olacaktır;

        //Soldier Rank
        public enum Rank
        {
            General, //General
            Colonel, //Albay
            LieutenantColonel, //Yarbay
            Major, //Binbaşı
            Captain, //Yüzbaşı
            Lieutenant //Teğmen
        }
    
  • Adım 2 (Composite Sınıfının Tasarlanması)
    Sıra ordu hiyerarşisinde alt kırılım noktalarına karşılık gelecek olan askerleri(komutan) oluşturmaya gelmiştir. Bu askerler emrinde bir veya birden fazla komutan veya asker barındıran görevliler olabilir.
        //Composite
        public class Commander : Soldier
        {
            List<Soldier> _soldiers = new();
            public Commander(string name, Rank rank) : base(name, rank)
            {
            }
    
            public override void AddSoldier(Soldier soldier)
                => _soldiers.Add(soldier);
    
            public override void RemoveSoldier(Soldier soldier)
                => _soldiers.Remove(soldier);
    
            public override void ExecuteOrder()
            {
                Console.WriteLine($"{_rank} - {_name}");
                foreach (Soldier soldier in _soldiers)
                    soldier.ExecuteOrder();
            }
        }
    

    Yukarıdaki kod bloğunu incelerseniz eğer ‘Commander’ sınıfı, emrinde birden fazla komutan ve asker barındıran(komutan seviyesinde) bir asker olduğu için ‘Soldier’ abstract class’ından türemektedir. Haliyle bu sınıf ilgili komutanın maiyetini temsil edecek olan 4. satırda tanımlanmış bir koleksiyon barındırmaktadır. Bu koleksiyon, ilgili askerin altındaki diğer askerleri temsil etmektedir. Dolayısıyla ilgili askerin maiyetine ‘AddSoldier’ ve ‘RemoveSoldier’ fonksiyonları eşliğinde askerler eklenmekte yahut varsa çıkarılmaktadır. Ayrıca ‘ExecuteOrder’ metoduna dikkat ederseniz eğer bu metot ile üstlerden gelen emirler doğrultusunda gerekli operasyonlar gerçekleştirilmekte ve içerisinde maiyeti üzerinde tek tek döngü ile işlemler gerçekleştirilerek hiyerarşik bir şekilde emri altındaki askerlere gelen emirler iletilmektedir.

  • Adım 3 (Leaf Sınıfının Tasarlanması)
    Ordu hiyerarşisinde, ağaç yapısının son halkası er olan askerlerdir. Haliyle bunlar tasarımımızda Leaf olarak nitelendirilmektedirler.
        //Leaf
        public class BuckPrivate : Soldier
        {
            public BuckPrivate(string name, Rank rank) : base(name, rank)
            {
            }
    
            //Bu fonksiyonun er olan askerler(Leaf) için bir anlamı yoktur!
            public override void AddSoldier(Soldier soldier)
                => throw new NotImplementedException();
    
            //Bu fonksiyonun er olan askerler(Leaf) için bir anlamı yoktur!
            public override void RemoveSoldier(Soldier soldier)
                => throw new NotImplementedException();
    
            public override void ExecuteOrder()
                => Console.WriteLine($"{_rank} - {_name}");
        }
    

    Yukarıdaki kod bloğuna göz atarsanız eğer ‘BuckPrivate’ sınıfı bir eri temsil ediyor olsa da özünde bir asker olduğu için yine ‘Soldier’ soyut sınıfından türemekte ve içerisinde gerekli implemantasyonları sağlamaktadır. Burada ilk dikkat edilmesini istediğim nokta ‘BuckPrivate’ın kendisinden sonra hiyerarşik açıdan başka bir askerin gelmediği, yani zincirin son halkası olduğudur. Haliyle bu nesne bir önceki ‘Commander’ sınıfında olduğu gibi bir koleksiyon vs. barındırmamaktadır. Bu sebepten dolayı komutanlardan gelen emirleri işleyen ‘ExecuteOrder’ metodu direkt o anki askere odaklı bir şekilde inşa edilmektedir.

    Bunların dışında ‘AddSoldier’ ve ‘RemoveSoldier’ fonksiyonları görüldüğü üzere er olan askerler için bir anlam ifade etmemektedir. Haliyle 1. adımda hafiften atıfta bulunduğumuz nokta burasıdır. Bu fonksiyonların, Leaf sınıfında işlevi olmadığı halde tanımlanmaları tasarımsal bir hatadan ziyade önceden de bahsedildiği üzere Arayüz Ayrım Prensibi‘ne aykırıdır.

    Peki bu duruma nasıl önlem alabiliriz?
    Bu duruma önlem alabilmenin yolu 1. adımda ki Component tasarımının arayüz ayrım prensibine uygun bir şekilde tasarlanmasından geçmektedir.

    Şöyle ki;

    Bir asker iki türlü olabilir. Ya emrinde farklı asker ve komutanlar barındıran bir asker ya da er! Dolayısıyla,

        //Component
        public abstract class Soldier
        {
            protected string _name;
            protected Rank _rank;
    
            protected Soldier(string name, Rank rank)
            {
                _name = name;
                _rank = rank;
            }
            public abstract void ExecuteOrder();
        }
    

    ‘Soldier’ sınıfı tüm askerlere genellenebilir şekilde yukarıdaki gibi tasarlanırken,

        //Component
        public abstract class SoldierComposite : Soldier
        {
            protected SoldierComposite(string name, Rank rank) : base(name, rank)
            {
            }
    
            public abstract void AddSoldier(Soldier soldier);
            public abstract void RemoveSoldier(Soldier soldier);
        }
    

    emrinde farklı asker ve komutanlar barındıran askerleri yukarıdaki gibi ‘SoldierComposite’ misali farklı bir sınıfta tasarlamak yeterli olacaktır. Nihayetinde bu iki sınıfta tasarımsal açıdan Component‘e karşılık gelecektir. ‘SoldierComposite’e göz atarsanız eğer Leaf için bir anlam ifade etmeyen tüm fonksiyonlar bu sınıf altında tanımlanmıştır.

    Tabi süreçte Composite ve Leaf sınıflarının da aşağıdaki gibi tasarlanması gerecektir.

        //Composite
        public class Commander : SoldierComposite
        {
            List<Soldier> _soldiers = new();
            public Commander(string name, Rank rank) : base(name, rank)
            {
            }
    
            public override void AddSoldier(Soldier soldier)
                => _soldiers.Add(soldier);
    
            public override void RemoveSoldier(Soldier soldier)
                => _soldiers.Remove(soldier);
    
            public override void ExecuteOrder()
            {
                Console.WriteLine($"{_rank} - {_name}");
                foreach (Soldier soldier in _soldiers)
                    soldier.ExecuteOrder();
            }
        }
    
        //Leaf
        public class BuckPrivate : Soldier
        {
            public BuckPrivate(string name, Rank rank) : base(name, rank)
            {
            }
    
            public override void ExecuteOrder()
                => Console.WriteLine($"{_rank} - {_name}");
        }
    

    Haliyle artık Leaf için anlamı olmayan fonksiyonlar lüzumsuz yere tanımlanmış olmayacaktır. Bundan sonra ilgili pattern’ın tatbik edilmesi amacıyla yazımızın devam sayfalarında vereceğimiz örneklerde direkt bu şekilde bir tasarım baz alınıp geliştirme yapılacaktır.

  • Adım 4 (Test)
    Yukarıdaki adımları tam teferruatlı bir şekilde gerçekleştirdiysek eğer artık Composite pattern inşası tamamlanmıştır demektir. Şimdi test edebiliriz.
            static void Main(string[] args)
            {
                //Composite general nesnes oluşturuyoruz.
                Commander general = new Commander("Gençay", Rank.General);
    
                //Composite general nesnesine Leaf nesneler ekliyoruz.
                general.AddSoldier(new BuckPrivate("Müslüm", Rank.Colonel));
                general.AddSoldier(new BuckPrivate("Orhan", Rank.Colonel));
    
                //Yeni Composite albay ve yarbay nesneleri oluşturuyoruz.
                Commander albay = new Commander("Hilmi", Rank.Major);
                Commander yarbay = new Commander("Cavit", Rank.LieutenantColonel);
    
                //Yarbay Composite nesnesine Leaf nesneler ekliyoruz.
                yarbay.AddSoldier(new BuckPrivate("Murat", Rank.Major));
                yarbay.AddSoldier(new BuckPrivate("Kaan", Rank.Major));
    
                //Albay Composite nesnesine yarbay nesnesini ekliyoruz.
                albay.AddSoldier(yarbay);
    
                //Albay Composite nesnesine Leaf nesne ekliyoruz.
                albay.AddSoldier(new BuckPrivate("Mustafa", Rank.LieutenantColonel));
    
                //Albay Composite nesnesini general nesnesine ekliyoruz.
                general.AddSoldier(albay);
    
                //General nesnesinden emri veriyoruz!
                general.ExecuteOrder();
            }
    

    Yukarıdaki kodu derleyip, çalıştırdığımızda aşağıdaki ekran görüntüsüyle karşılaşmaktayız.
    İşte bu kadar… 🙂
    Görüldüğü üzere hiyerarşideki tüm nesneler tek tek tetiklenerek ordu yapılanmasında en başta verilen emir en alttaki askere kadar işletilerek, eriştirilmiştir.

Client açısından tekil nesneler ile nesne grupları arasındaki farklılıkları görmeksizin işlem yapmak için Composite pattern kullanılabilir.

Uygulanabilirlik

Yukarıda görüldüğü üzere Composite pattern, Leaf ve Composite yapılarından oluşan ve bu yapılar arasında stratejik bir ayrım sağlayan tasarım desenidir. İhtiyaç olarak ağaç yapısı(bir başka deyişle hiyerarşik) operasyonlarında özyinelemeli(recursive) nesne yapısı oluşturulmasına olanak sağlamaktadır. Bu recursive operasyon esnasında client, hem Leaf hem de Composite yapıları ekstra çabalara girişmeksizin aynı şekilde(fark yaratmaksızın) işlemektedir.

Lehte ve Aleyhte Durumlar
Karmaşık ağaç yapılarıyla rahatlıkla çalışılabilir. İşlevselliği çok farklı olan sınıflar için ortak bir arayüz sağlamak zor olabilir. Belirli senaryolarda Component arayüzünü aşırı genelleştirmek gerekebilir. Haliyle bu durumda ilgili tasarımın anlaşılmasını zorlaştırabilir.
Nesne ağacıyla çalışan nesneleri mevcut kodu bozmadan uygulamaya ekleyebildiğimizden dolayı Open Closed prensibiyle uyumludur. Composite pattern, yukarıdaki senaryonun 3. adımında bahsedildiği üzere Interface Segregation prensibine aykırı olabilir.
Daha az bellek yönetimi gerektirmektedir.
Esnektir.

İlkel(Leaf) ve karmaşık(Composite) nesnelerin işlenmesi farklı şekilde ele alınır. Haliyle her nesneyi işlemeden önce türünü sorgulama zahmetine katlanmaksızın Composite pattern ile hızlı çözüm getirebilirsiniz.

Diğer Kalıplarla İlişkisi
Genel Görüşler

Composite pattern’ın tüm amacı birbirleriyle ilişkili karmaşık(composite) yapıların tıpkı bir yaprak gibi atomik olarak işlenebilmesidir. Nihayetinde bu kalıbın merkezinde, bir client’ın içinde bir çok nesne olduğunu bilmesine gerek kalmaksızın ilkel ve karmaşık nesneler(ağaç yapısı) üzerinde işlem yapabilme yeteği yatmaktadır.

Composite pattern ile heterojen olan ilkel(Leaf) ve karmaşık(Composite) nesne koleksiyonunu atomik olarak ele alabilmek için gerekli fonksiyonların Component arayüzünde tanımlanması yeterlidir. Ancak bu tanımlama, önceki satırlarda da değindiğimiz gibi(özellikle pratik örneğin 3. adımında) Leaf nesnelerinde de implemantasyona neden olacağından dolayı olası güvensizliğe mâl olabilir. Çünkü client’lar Leaf nesneler üzerinden Composite nesnelere özel olan fonksiyonları kullanmak gibi anlamsız eylemler yapabilir. Halbuki Component‘te tanımlı olan composite fonksiyonları Leaf nesneleri için bir anlam ifade etmeyecektir. Haliyle bu durum daha önceden de bahsedildiği gibi Arayüz Ayrım Prensibine aykırı olabilir. Bunu engellemek için Component‘i ikiye ayırıp composite fonksiyonlarını farklı bir arayüze alabilir ve Composite nesneleri o arayüzden türetebilirsiniz.

Evet, böylece bir design pattern makalesinin daha sonuna gelmiş olduk. Sonraki sayfalarda konuya dair farklı senaryolarda bol pratik örnekler sergilemekteyim. Kesinlikle göz atmanızı ve mümkünse teoride bırakmayıp pratik ile desteklemenizi tavsiye ederim.

İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…

SENARYO 2
Bir şirkette aşağıdaki hiyerarşide personel pozisyonları bulunmaktadır.
  • Proje Yöneticisi
    • Takım Lideri
      • Yazılım Geliştirici

Her bir pozisyonda bulunan personel, hiyerarşik olarak kendisinden sonra gelen herhangi bir pozisyondaki personeli maiyetinde barındırabilmektedir.

Bu senaryoda herhangi bir pozisyondaki personelin, maiyetindeki personellerin bilgilerini ve toplam maaşlarını hesaplayıp karşılığına yazdıralım.

ÇÖZÜM
  • Adım 1 (Component Soyut Sınıfının Tasarlanması)
    Composite pattern’ın tasarımında ilk olarak Leaf ve Composite nesnelerimizin ortak arayüzü olan Component abstract class’ı tasarlanarak başlanmaktadır. Haliyle bizler de vakit kaybetmeksizin ilk olarak ilgili sınıfı oluşturarak başlıyoruz.
        //Component
        public abstract class Employee
        {
            protected string _name;
            protected double _salary;
            protected Employee(string name, double salary)
            {
                _name = name;
                _salary = salary;
            }
    
            public string GetName()
                => _name;
            public double GetSalary()
                => _salary;
            public abstract void Print();
        }
    

    Yukarıdaki ‘Employee’ adıyla tasarlanan Component sınıfı direkt Leaf ve Composite nesnelerinin kullanabileceği şekilde ortak member’larla tasarlanmıştır. Sadece Composite nesnelerine özel fonksiyonları barındıran Component tasarımı ise aşağıdaki olacaktır. Böylece hem Leaf nesnelerinde anlamsız member’lar olmayacak hem de arayüz ayrım prensibini sağlamış bulunacağız.

    Ayrıca yukarıdaki Component örneğinde ‘GetName’ ve ‘GetSalary’ metotlarının gövdeleri direkt oluşturulmuşken, ‘Print’ metodunun gövdesi concrete olacak olan Composite ve Leaf nesnelerine bırakılmıştır. Nihayetinde bu kod, buradaki davranışın ihtiyaca göre tarafımızca belirlendiğine bir örnektir. Misal bizim concrete nesnelerde maaşlarla ilgili ekstradan işlemler yapmamız gerekseydi ‘GetSalary’ metodu abstract olarak ayarlayıp, bu işi ilgili nesnesinde inşaya bırakabilirdik. Ama gerekmediği için kıssadan hisse yapıp gerekli işlemi burada gerçekleştirmekteyiz.

        //Component
        public abstract class EmployeeComposite : Employee
        {
            protected List<Employee> _employees = new();
            protected EmployeeComposite(string name, double salary) : base(name, salary)
            {
            }
            public void AddEmployee(Employee employee)
                => _employees.Add(employee);
            public void RemoveEmployee(Employee employee)
                => _employees.Remove(employee);
        }
    

    Composite nesneleri için özel olan fonksiyonları barındıran Component soyut sınıfımızda yukarıdaki gibi olacaktır. Burada da ‘AddEmployee’ ve ‘RemoveEmployee’ metotlarının gövdelerini aynı mantıkla concrete olan Composite nesnelerine bırakmaksızın direkt olarak inşa etmiş bulunmaktayız. Haliyle ilgili metotlar sadece ekleme ve çıkarma işlemleri yapacaklarından ve ekstra bir iş yürütmeyeceklerinden dolayı direkt bu ihtiyacı burada karşılamış bulunmaktayız.

  • Adım 2 (Composite Sınıfının Tasarlanması)
    Şimdi sıra, senaryo gereği altında en az bir pozisyondan personel barındıracak olan Composite sınıfını tasarlamaya gelmiş bulunmaktadır.
        //Component
        public class Manager : EmployeeComposite
        {
            public Manager(string name, double salary) : base(name, salary)
            {
            }
            public override void Print()
            {
                Console.WriteLine($"Name\t\t:{GetName()}");
                Console.WriteLine($"Salary\t\t:{GetSalary()}");
                double totalSalary = 0;
                _employees.ForEach(employee =>
                {
                    totalSalary += employee.GetSalary();
                    employee.Print();
                });
                Console.WriteLine($"Total Salary\t:{totalSalary}");
            }
        }
    

    Yukarıdaki ‘Manager’ isimli Component sınıfına göz atarsanız eğer ‘Print’ fonksiyonu ile ilgili personelin adını ve maaşını ekrana yazdırmakla birlikte altındaki elemanların da(ki bu elemanlar normal personel olabileceği gibi yönetici pozisyonunda bir personel de olabilir) toplam maaşını hesaplamakta ve ‘Print’ fonksiyonlarını tetikleyerek onlara dair bilgileri ekrana yazdırmaktadır.

  • Adım 3 (Leaf Sınıfının Tasarlanması)
    Şimdi ise altında herhangi bir pozisyondan personel barındırmayan personelleri temsil edecek olan Leaf sınıfını tasarlamaya sıra gelmiştir.
        //Leaf
        public class SoftwareDeveloper : Employee
        {
            public SoftwareDeveloper(string name, double salary) : base(name, salary)
            {
            }
            public override void Print()
            {
                Console.WriteLine($"\tName\t\t:{GetName()}");
                Console.WriteLine($"\tSalary\t\t:{GetSalary()}");
            }
        }
    

    Yukarıdaki ‘SoftwareDeveloper’ isimli Leaf nesnesinde ise ‘Print’ fonksiyonuyla sadece ilgili personele dair adı ve maaş bilgileri ekrana yazdırılmaktadır.

  • Adım 4 (Test)
    Bu ana kadar dikkat ederseniz; ‘Proje Yöneticisi’ ve ‘Takım Lideri’ pozisyonları ‘Manager’ sınıfıyla, ‘Yazılım Geliştirici’ pozisyonu ise ‘SoftwareDeveloper’ sınıfıyla temsil edilmektedir. Artık yapılan bu tasarımı rahatlıkla test edebiliriz.
            static void Main(string[] args)
            {
                Manager projectManager = new("Ahmet", 1000);
                Manager teamLeader1 = new("Mehmet", 500);
                Manager teamLeader2 = new("Hilmi", 450);
    
                SoftwareDeveloper softwareDeveloper1 = new("Çoşgun", 250);
                SoftwareDeveloper softwareDeveloper2 = new("Murat", 150);
                SoftwareDeveloper softwareDeveloper3 = new("Lale", 160);
                SoftwareDeveloper softwareDeveloper4 = new("Cümbüş", 270);
    
                teamLeader1.AddEmployee(softwareDeveloper1);
                teamLeader1.AddEmployee(softwareDeveloper2);
                teamLeader1.AddEmployee(softwareDeveloper3);
                teamLeader2.AddEmployee(softwareDeveloper4);
    
                projectManager.AddEmployee(teamLeader1);
                projectManager.AddEmployee(teamLeader2);
    
                projectManager.Print();
            }
    

    Yukarıdaki örnek kodu derleyip, çalıştırdığımızda aşağıdaki çıktıyı verecektir.

    Görüldüğü üzere tüm pozisyonlardaki personellerin ve maiyetlerindeki diğer personellerin toplam maaşla birlikte bilgileri yazdırılmış bulunmaktadır.

İyi çalışmalar…

SENARYO 3
Bir uygulamadaki hiyerarşik menülerin Composite pattern ile tasarımını gerçekleştirelim.
ÇÖZÜM
  • Adım 1 (Component Soyut Sınıfının Tasarlanması)
    Bir menü, tek seviyelik bir menü olabileceği gibi(Leaf), altında birden fazla farklı menü barındıran karmaşık(Composite) bir menüde olabilir. Haliyle her iki türlüde ilgili menülerin ortak alanlarını barındıracak olan Component arayüzünü tasarlayalım.
        //Component
        abstract class Menu
        {
            protected string _name;
            protected string _description;
            protected Menu(string name, string description)
            {
                _name = name;
                _description = description;
            }
            public string GetName()
                => _name;
            public string GetDescription()
                => _description;
            public abstract void Print(int level = 0);
        }
    
        //Component
        abstract class MenuComposite : Menu
        {
            protected List<Menu> _menus = new();
            public MenuComposite(string name, string description) : base(name, description)
            {
            }
            public void AddMenu(Menu menu)
                => _menus.Add(menu);
            public void RemoveMenu(Menu menu)
                => _menus.Remove(menu);
        }
    

    Yukarıdaki ‘Menu’ Component arayüzündeki ‘Print’ fonksiyonuna göz atarsanız eğer int türünden ‘level’ isminde bir parametre almaktadır. Bunun sebebi, ilgili menüleri listelerken derecelerine göre içe kıvrımlı bir şekilde görsellik oluşturabilmek içindir.

  • Adım 2 (Composite Sınıfının Tasarlanması)
    Ardından sırada Leaf ve Composite nesneleri oluşturmak vardır. Siz istediğinizden başlayabilirsiniz. Ben deniz Composite nesneyi tasarlayarak devam ediyorum.
        //Composite
        class MenuItems : MenuComposite
        {
            public MenuItems(string name, string description) : base(name, description)
            {
            }
    
            public override void Print(int level = 0)
            {
                Console.WriteLine($"{(level == 0 ? "" : "\t".PadRight(level, '\t'))}{GetName()} {{{GetDescription()}}}");
                foreach (Menu menu in _menus)
                    menu.Print(menu is MenuComposite ? level + 1 : level + 2);
            }
        }
    
  • Adım 3 (Leaf Sınıfının Tasarlanması)
    Benzer şekilde Leaf nesnesini de tasarlarsak eğer;
        class MenuItem : Menu
        {
            public MenuItem(string name, string description) : base(name, description)
            {
            }
    
            public override void Print(int level = 0)
                => Console.WriteLine($"{"\t".PadRight(level, '\t')}{GetName()} {{{GetDescription()}}}");
        }
    
  • Adım 4 (Test)
    Evet, görüldüğü üzere tasarımsal inşa tamamlanmış bulunmaktadır. Şimdi sıra inşa edilen bu tasarımı test etmeye gelmiştir.
            static void Main(string[] args)
            {
                MenuItems menuItems1 = new("Ana Menü 1", "Bu ana menü 1'dir...");
                menuItems1.AddMenu(new MenuItem("Menü 1", "Bu alt menü 1'dir..."));
                menuItems1.AddMenu(new MenuItem("Menü 2", "Bu alt menü 2'dir..."));
                menuItems1.AddMenu(new MenuItem("Menü 3", "Bu alt menü 3'dir..."));
    
                MenuItems menuItems2 = new("Ana Menü 2", "Bu ana menü 2'dir...");
                menuItems2.AddMenu(new MenuItem("Menü 4", "Bu alt menü 4'dür..."));
    
                MenuItems menuItems3 = new("Ana Menü 3", "Bu ana menü 3'dür...");
                menuItems3.AddMenu(new MenuItem("Menü 5", "Bu alt menü 5'dir..."));
                menuItems3.AddMenu(new MenuItem("Menü 6", "Bu alt menü 6'dir..."));
                menuItems3.AddMenu(new MenuItem("Menü 7", "Bu alt menü 7'dir..."));
    
                menuItems1.AddMenu(menuItems2);
                menuItems1.AddMenu(menuItems3);
    
                menuItems2.AddMenu(new MenuItem("Menü 8", "Bu alt menü 8'dir..."));
    
                MenuItems menuItems4 = new("Ana Menü 4", "Bu ana menü 4'dür...");
                menuItems4.AddMenu(new MenuItem("Menü 9", "Bu alt menü 9'dur..."));
                menuItems4.AddMenu(new MenuItem("Menü 10", "Bu alt menü 10'dur..."));
                menuItems4.AddMenu(new MenuItem("Menü 11", "Bu alt menü 11'dur..."));
    
                MenuItems menuItems5 = new("Ana Menü 5", "Bu ana menü 5'dir...");
                menuItems5.AddMenu(new MenuItem("Menü 12", "Bu alt menü 12'dir..."));
                menuItems5.AddMenu(new MenuItem("Menü 13", "Bu alt menü 13'dir..."));
                menuItems5.AddMenu(new MenuItem("Menü 14", "Bu alt menü 14'dir..."));
    
                menuItems4.AddMenu(menuItems5);
                menuItems3.AddMenu(menuItems4);
    
                menuItems1.Print();
            }
    

    Yukarıdaki dolu dizgin menü verisiyle doldurulmuş örnek kod bloğunu derleyip, çalıştırırsak eğer aşağıdaki gibi bir ekran çıktısıyla karşılacağız.
    Görüldüğü üzere her menü seviyesine göre içe kıvrımlı bir şekilde menüler ekrana listeletilmiştir.

İyi çalışlar…

SENARYO 4
Bilgisayar ve bilgisayar parçaları satışı yapan bir mağazanın, bilgisayar toplama sürecini Composite pattern ile simüle edelim. Her bir bilgisayar parçasının fiyatını görebilelim ve bilgisayar toplandığı taktirde toplam maliyeti de elde edebilelim.
ÇÖZÜM
  • Adım 1 (Component Soyut Sınıfının Tasarlanması)
    Herşeyden önce bilgisayardaki tüm parçaları temsil edecek olan Component arayüzünü tasarlayarak başlayalım.

    Çoğu bilgisayar parçası tek başına kullanılmaktadır. Dolayısıyla aşağıdaki Component ilkel yani Leaf olarak nitelendirebileceğimiz bilgisayar parçarını temsil edecektir.

        //Component
        abstract class Part
        {
            protected string _brand;
            protected double _price;
            protected Part(string brand, double price)
            {
                _brand = brand;
                _price = price;
            }
            public virtual double GetPrice()
                => _price;
            public string GetBrand()
                => _brand;
        }
    

    Kimi parçalarda birden fazla parçanın takılmasıyla meydana gelir. Genel anlamda bu parçadan ziyade bilgisayarın ta kendisidir de diyebiliriz. Haliyle şimdide de Composite nitelikteki parçaları ya da bilgisayarı temsil edecek olan Component‘i tasarlayalım.

        //Component
        abstract class PartComposite : Part
        {
            protected List<Part> _parts = new();
            protected PartComposite(string brand, double price) : base(brand, price)
            {
            }
            public void AddPart(Part part)
                => _parts.Add(part);
            public void RemovePart(Part part)
                => _parts.Add(part);
            public override double GetPrice()
            {
                double totalPrice = _price;
                Console.WriteLine($"{_brand} \t\t: {_price}");
                foreach (Part part in _parts)
                {
                    Console.WriteLine($"\t{part.GetBrand()} \t: {part.GetPrice()}");
                    totalPrice += part.GetPrice();
                }
                return totalPrice;
            }
        }
    

    Yukarıdaki kod bloğunu incelerseniz eğer ‘Part’ sınıfında virtual olarak ayarlanmış olan ‘GetPrice’ metodu ‘PartComposite’ sınıfında override edilerek içerisinde tüm Composite nesneler için hali hazırda toplam maliyet hesaplaması yapılmaktadır. Bu işlemi tek tek Composite nesnelerde de yapabilirdik lakin tek elden burada yapmak hem daha az maliyetli hem de ekstralara ihtiyaç olmadığı için daha doğru olacaktır.

  • Adım 2 (Composite Sınıfının Tasarlanması)
    Ben senaryo gereği tüm parçaların birleştirilmiş hali bilgisayar olarak düşündüğümden dolayı Composite nesne olarak da bilgisayarı temsil edeceğim. Buradaki tercih sizlere kalmış. Farklı parçaları da Composite olarak temsil edebilir ve tasarlayabilirsiniz.
        //Composite
        class Computer : PartComposite
        {
            public Computer(string brand, double price) : base(brand, price)
            {
            }
        }
    

    Composite sınıfımızın görüldüğü üzere yukarıdaki gibi ‘PartComposite’den kalıtım alacak şekilde tasarlanması yeterlidir. Nihayetinde alt parçaları barındıran koleksiyon dahil gerekli tüm fonksiyonlar base class’tan geliyor olacaktır.

  • Adım 3 (Leaf Sınıfının Tasarlanması)
    Şimdi sıra Leaf sınıflarımızı tasarlamaya gelmiş bulunmaktadır. Sınıflarımızı diyorum çünkü birden fazla bilgisayar parçası mevcuttur. Dolayısıyla tüm parçalara karşılık bir nesne tasarımı gerçekleştireceğiz.(Tabi burada tek bir nesne üzerinden de her bir Leaf‘i instance olarak temsil edebilirdik. Haliyle böyle bir yaklaşımın kararı sizinle ihtiyaçlarınıza kalmıştır)
        //Leaf
        class Monitor : Part
        {
            public Monitor(string brand, double price) : base(brand, price)
            {
            }
        }
    
        //Leaf
        class Keyboard : Part
        {
            public Keyboard(string brand, double price) : base(brand, price)
            {
            }
        }
    
        //Leaf
        class Ram : Part
        {
            public Ram(string brand, double price) : base(brand, price)
            {
            }
        }
    
        //Leaf
        class Mouse : Part
        {
            public Mouse(string brand, double price) : base(brand, price)
            {
            }
        }
    
  • Adım 4 (Test)
    Haliyle sıra son olarak yapılan bu tasarımı test etmeye gelmiştir.
            static void Main(string[] args)
            {
                Computer computer = new("Asus", 10000);
                computer.AddPart(new Monitor("Asus", 2750));
                computer.AddPart(new Keyboard("A4Tech", 750));
                computer.AddPart(new Ram("XXX", 1750));
                computer.AddPart(new Mouse("Hp", 125));
    
                double totalPrice = computer.GetPrice();
                Console.WriteLine($"Toplam Fiyat \t: {totalPrice}");
            }
    

    Yukarıdaki kod bloğunu derleyip, çalıştırdığımızda aşağıdaki ekran görüntüsüyle karşılaşacağız.
    İşte bu kadar 🙂

İyi çalışmalar…

SENARYO 5
Bir firma aylık olarak personellerine ödediği maaşların raporlanabileceği bir uygulama istemektedir. Firma bünyesinde aşağıdaki gibi bir hiyerarşi mevcuttur;
  • Genel müdürlük
    • Müdürlük
      • Bölge
        • Bayi
          • Personel

Bu hiyerarşide; genel müdürlüğe bağlı müdürlükler, müdürlüklere bağlı bölgeler, bölgelere bağlı bayiler ve son olarak bu bayilerde çalışan personeller olduğu görülmektedir.
Firma maaş hesaplamayı, hem tüm şirket için genel olarak hem de hususi müdürlük, bölge, bayi gibi farklı hiyerarşi seviyeleri için özel olarak istemektedir.

ÇÖZÜM
  • Adım 1 (Component Soyut Sınıfının Tasarlanması)
        //Component
        abstract class Personnel
        {
            protected string _name;
            public double Salary { get; set; }
            protected Personnel(string name)
            {
                _name = name;
            }
            public virtual double ComputeSalary()
            {
                Console.WriteLine($"{_name} -> Price : {Salary}");
                return Salary;
            }
        }
    
        //Component
        abstract class PersonnelComposite : Personnel
        {
            protected List<Personnel> _personnels = new();
            protected PersonnelComposite(string name) : base(name)
            {
            }
    
            public void AddPersonnel(Personnel personnel)
                => _personnels.Add(personnel);
            public void RemovePersonnel(Personnel personnel)
                => _personnels.Remove(personnel);
            public override double ComputeSalary()
            {
                double totalSalary = 0;
                foreach (Personnel personnel in _personnels)
                    totalSalary += personnel.ComputeSalary();
    
                Console.WriteLine($"{_name} Total Salary : ${totalSalary}");
                Console.WriteLine("******************");
                return totalSalary;
            }
        }
    
  • Adım 2 (Composite Sınıfının Tasarlanması)
        //Composite
        class GeneralDirectorate : PersonnelComposite
        {
            public GeneralDirectorate(string name) : base(name)
            {
            }
        }
    
        //Composite
        class Director : PersonnelComposite
        {
            public Director(string name) : base(name)
            {
            }
        }
    
        //Composite
        class Area : PersonnelComposite
        {
            public Area(string name) : base(name)
            {
            }
        }
    
        //Composite 
        class Seller : PersonnelComposite
        {
            public Seller(string name) : base(name)
            {
            }
        }
    
  • Adım 3 (Leaf Sınıfının Tasarlanması)
        //Leaf 
        class Worker : Personnel
        {
            public Worker(string name) : base(name)
            {
            }
        }
    
  • Adım 4 (Test)
            static void Main(string[] args)
            {
                //İşçiler tanımlanıyor
                Worker personnel1 = new("Ahmet") { Salary = 10000 };
                Worker personnel2 = new("Mehmet") { Salary = 8000 };
                Worker personnel3 = new("Necati") { Salary = 12000 };
                Worker personnel4 = new("Hilmi") { Salary = 7000 };
                Worker personnel5 = new("Rıfkı") { Salary = 3000 };
                Worker personnel6 = new("Muiddin") { Salary = 2500 };
                Worker personnel7 = new("Rauf") { Salary = 13000 };
                Worker personnel8 = new("Şuayip") { Salary = 15000 };
    
                //Bayiler tanımlanıyor
                Seller seller1 = new("Yenimahalle Bayi");
                Seller seller2 = new("Çankaya Bayi");
                Seller seller3 = new("Bahçelievler Bayi");
    
                //İşçiler çalıştıkları bayilere ekleniyor
                seller1.AddPersonnel(personnel1);
                seller1.AddPersonnel(personnel2);
                seller1.AddPersonnel(personnel3);
    
                seller2.AddPersonnel(personnel4);
                seller2.AddPersonnel(personnel5);
                seller2.AddPersonnel(personnel6);
    
                seller3.AddPersonnel(personnel7);
                seller3.AddPersonnel(personnel8);
    
                //Bölgeler tanımlanıyor
                Area area1 = new("Ankara Bölgesi");
                Area area2 = new("İstanbul Bölgesi");
    
                //Bölgelere bayiler ekleniyor
                area1.AddPersonnel(seller1);
                area1.AddPersonnel(seller2);
                area2.AddPersonnel(seller3);
    
                //Müdürler tanımlanıyor
                Director director1 = new("Kuzey Grubu Müdürü");
                Director director2 = new("Güney Grubu Müdürü");
    
                //Müdürlere bölgeler ekleniyor
                director1.AddPersonnel(area2);
                director2.AddPersonnel(area1);
    
                //Genel müdür tanımlanıyor
                GeneralDirectorate generalDirectorate = new("Genel Müdür");
    
                //Genel müdüre müdürler ekleniyor
                generalDirectorate.AddPersonnel(director1);
                generalDirectorate.AddPersonnel(director2);
    
                generalDirectorate.ComputeSalary();
            }
    

    Sonuç :

İyi çalışmalar…

SENARYO 6
Bir cümle, kelimelerden; kelime ise harflerden meydana gelmektedir. Haliyle böyle bir metinsel düzeni Composite pattern ile gerçekleştirelim.
ÇÖZÜM
  • Adım 1 (Component Soyut Sınıfının Tasarlanması)
    Bir metni ifade edecek olan; cümle, kelime ve harf diziliminin ortak yapılanması olan Component arayüzü aşağıdaki gibi olacaktır.
        //Component
        abstract class Text
        {
            public abstract void PrintThisBefore();
            public abstract void PrintThisAfter();
            public virtual void Print()
            {
                PrintThisBefore();
                PrintThisAfter();
            }
        }
    

    Burada dikkat ederseniz ‘PrintThisBefore’ ve ‘PrintThisAfter’ metotları mevcuttur. Bu metotlar ile ilgili söz öğesinin öncesi ve sonrası durumlarına göre Composite ve Leaf sınıflarında gerekli işaretlemelerde bulunuyor olacağız. Misal olarak; kelimelerden önce boşluğun gelmesi ya da cümlelerin sonunda nokta gelmesi gibi…

        //Component
        abstract class TextComposite : Text
        {
            protected List<Text> _texts = new();
            public void AddText(Text text)
                => _texts.Add(text);
            public void RemoveText(Text text)
                => _texts.Remove(text);
            public override void Print()
            {
                PrintThisBefore();
                foreach (Text text in _texts)
                    text.Print();
                PrintThisAfter();
            }
        }
    
  • Adım 2 (Composite Sınıfının Tasarlanması)
    Bir metin yapılanmasında kelimeler ve cümleler olmak üzere iki adet Composite sınıf bulunmaktadır. Şimdi gelin bu iki sınıfı sırasıyla tasarlayalım;
        //Composite
        class Word : TextComposite
        {
            public override void PrintThisBefore()
                => Console.Write(" ");
            public override void PrintThisAfter() { /* Boş */ }
        }
    

    ‘Word’ sınıfına göz atarsanız eğer ‘PrintThisBefore’ metodunda her kelimeden önce gelecek olan boşluk karakteri yazdırılmaktadır. ‘PrintThisAfter’ metodunda ise, her kelimeden sonra boşluk ya da nokta gelebilme ihtimali olduğu için herhangi bir işlem gerçekleştirilmemektedir.

        //Composite
        class Sentence : TextComposite
        {
            public override void PrintThisBefore() { /* Boş */ }
            public override void PrintThisAfter()
                => Console.Write(".");
        }
    

    Benzer mantıkla yukarıdaki ‘Sentence’ sınıfında ise ‘PrinThisBefore’ metodu boş geçilmiş lakin ‘PrinThisAfter’ metodunda ise her cümle sonunda nokta geleceği ifade edilmiştir.

  • Adım 3 (Leaf Sınıfının Tasarlanması)
    Metin dizelerinde Leaf olarak tasarlanabilecek tek yapı harflerdir. Haliyle hemen ilgili sınıf tasarımını gerçekleştirelim;
        //Leaf
        class Letter : Text
        {
            char _character;
    
            public Letter(char character)
            {
                _character = character;
            }
            public override void PrintThisBefore()
                => Console.Write(_character);
            public override void PrintThisAfter() { /* Boş */ }
        }
    

    ‘Letter’ sınıfına göz atarsanız eğer bir ‘char’ türünden karakter almakta ve ‘PrinThisBefore’ metodunda bu karakteri ekrana yazdırmaktadır.

  • Adım 4 (Test)
    Yapılan bu inşayı aşağıdaki örnek çalışmayla test edersek eğer;
            static void Main(string[] args)
            {
                Word word1 = new();
                word1.AddText(new Letter('M'));
                word1.AddText(new Letter('e'));
                word1.AddText(new Letter('r'));
                word1.AddText(new Letter('h'));
                word1.AddText(new Letter('a'));
                word1.AddText(new Letter('b'));
                word1.AddText(new Letter('a'));
    
                Word word2 = new();
                word2.AddText(new Letter('D'));
                word2.AddText(new Letter('ü'));
                word2.AddText(new Letter('n'));
                word2.AddText(new Letter('y'));
                word2.AddText(new Letter('a'));
    
                Sentence sentence = new();
                sentence.AddText(word1);
                sentence.AddText(word2);
    
                sentence.Print();
            }
    

    ekran çıktısını alacağız.

    Haliyle görüldüğü üzere Composite pattern mantığıyla harflerden kelimeler, kelimelerden de cümleler oluşturmuş bulunmaktayız 🙂

İyi çalışmalar…

Exit mobile version