Design Patterns (Tasarım Kalıpları) - Builder Pattern

Yazılım geliştirme süreçlerinde karşımıza çıkan sorunlara tekrar edilebilir çözümler sunan desenlerdir. Bir başka deyişle, bir probleme çözüm yaklaşımı için genel şablonlardır. Programlama dilinden bağımsız olma sebebi de budur. Bir yazılım hangi dille geliştiriliyor olursa olsun, karşılaşabileceğiniz sorunların bir çoğuna tasarım kalıpları bir çözüm sunar ve sorunların nasıl daha kolay çözebileceğini gösterir.

Tasarım kalıpları hayatımıza nesneye dayalı programlama (object-oriented programming) ile girdi diyebiliriz. Sınıf(class) ve nesnelerin(object) oluşum, ilklendirme, birleşirme ve iletişim kurması üzerine geliştirilmişlerdir. Daha geliştirilebilir ve kaliteli bir kod yazmamıza olanak sağlamakla kalmaz; aynı zamanda diğer yazılımcılarla okunabilirlik düzeyinde iyi bir iletişim kurmamızı sağlar.

3 ana başlık altında topluyoruz:

  • Yaratımsal tasarım kalıpları (creational design patterns)
  • Yapısal tasarım kalıpları (structural design patterns)
  • Davranışsal tasarım kalıpları (behavioral design patterns)

Bu yazımda yaratımsal tasarım kalıplarından "builder" tasarım kalıbını inceleyeğiz. Örneklerde C# ve Java dilini kullanacağız.

Kodlara ulaşmak için;
https://github.com/demiremrece/Design-Patterns

Builder Design Pattern

Bu tasarım kalıbını aslında gerek .net yazarken gerek java yazarken gerekse başka dillerde yazılım geliştirirken çok sıklıkla kullanıyoruz. Şahsen benim en çok beğendiğim kalıplardan biridir. Eğer kompleks bir constructor yapımız var ve parametre çeşitliği fazla ise bu patterni kullanabiliriz.

Javada birçok parametre varyasyonlarıyla constructorlar oluşturulup hiyerarşiye göre birbirlerini çağırmasına telescoping constructor yapısı denir.

Pizza(int size) { 
    this.size = size;
}       
Pizza(int size, boolean cheese) { 
    this(size);
    this.cheese = cheese;
}   
Pizza(int size, boolean cheese, boolean pepperoni) { ... }   
Pizza(int size, boolean cheese, boolean pepperoni, boolean mushroom) { ... }

Buradaki sıkıntı, parametre sayısı arttıkça parametrelerin sıralarının karıştırılabilmesi ve hatırlanmasının zor olması.

Bunun üstesinden gelmek için ise parametreler yerine objelerle kontrol edilen bir yapılandırma kullanıyoruz. Bu bizi aynı zamanda gereksiz yere bir çok setter metodlar yazmaktan da kurtarıyor.

Patterni Oluşturalım

Öğle yemeği için hamburger siparişini nesneleştirmişiz gibi düşünelim. Hamburgerin nasıl olacağına dair bir çok seçeneğimiz olur. Ekmek cinsi, tosta bastırılmış mı, beyaz et mi yoksa kırmızı et mi istiyoruz, sos olsun mu gibi. Siparişimizi builder pattern kullanarak oluşturabildiğimiz bir yapı tasarlayalım.

    public class Hamburger
    {
        public Bread bread;
        public bool isToasted;
        public List<Condiments> condiments;
        public Meat meat;

        public Hamburger(Bread bread, bool isToasted, List<Condiments> condiments, Meat meat){
            this.bread = bread;
            this.isToasted = isToasted;
            this.condiments = condiments;
            this.meat = meat;
        }
    }

Yukarıda gördüğünüz gibi hiç setter kullanmadık. Hamburger sınıfında sadece 1 constructor bulunuyor. Farklı parametre çiftleriyle constructor çeşitliliğini artırmadık.

// Et, ekmek ve sos türleri için enum tanımladım.

    public enum Meat{
        Chicken,
        Beef,
        Turkey
    }

    public enum Bread{
        Wholewheat,
        Normal
    }

    public enum Condiments{
        BBQ,
        Ketchap,
        Mayonnaise
    }
    public class HamburgerBuilder{
        private Bread bread;
        private bool isToasted;
        private List<Condiments> condiments = new List<Condiments>();
        private Meat meat;

        public HamburgerBuilder Bread(Bread bread){
            this.bread = bread;   
            return this;
        }

        public HamburgerBuilder Meat(Meat meat){
            this.meat = meat;   
            return this;
        }

        public HamburgerBuilder Toasted(bool isToasted){
            this.isToasted = isToasted;
            return this;
        }

        public HamburgerBuilder AddCondiment(Condiments condiment){
            this.condiments.Add(condiment);
            return this;
        }

        public Hamburger Build()
        {
            return new Hamburger(bread, isToasted, condiments, meat);
        }
    }

Dikkat ederseniz Hamburger sınıfında bulunan özelliklerin hepsi HamburgerBuilder içerisinde de tanımlanmış durumda. Build metodu hariç diğer metodların hepsi HamburgerBuilder tipinde bir nesne döndürüyor. BuilderPattern'in bize sağladığı kolaylık da buradan geliyor. Build metodu ise siparişimizin sonucu olan Hamburger tipinde bir nesne üretiyor. Aşağıdaki örnekte bu işleyişi nasıl uygulayacağımızı gösterdim. Bu sayede daha anlaşılır olacağını düşünüyorum.

// LaunchOrder.cs

    public class LaunchOrder
    {
        public static void OrderHamburger(Hamburger hamburger)
        {
            Console.WriteLine("Bread: "+ hamburger.bread);
            Console.WriteLine("Meat: "+ hamburger.meat);
            Console.WriteLine("isToasted: "+ hamburger.isToasted);

            Console.Write("Condiments: ");
            foreach (var condiment in hamburger.condiments)
            {
                Console.Write(condiment + " ");
            }

        }
    }

Kullanımı

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

            Hamburger hamburger =
                new HamburgerBuilder()
                    .Meat(Meat.Chicken)
                    .Bread(Bread.Wholewheat)
                    .Toasted(false)
                    .AddCondiment(Condiments.BBQ)
                    .AddCondiment(Condiments.Ketchap)
                    .Build();
            
            LaunchOrder.OrderHamburger(hamburger);

        }
    }
}


/* --- Çıktı ---

Bread: Wholewheat
Meat: Chicken
isToasted: False
Condiments: BBQ Ketchap 

*/