Builder Pattern - Oluşturucu Tasarım Deseni
11 min read

Builder Pattern - Oluşturucu Tasarım Deseni

Builder Pattern - Oluşturucu Tasarım Deseni

Builder pattern, karmaşık nesneleri adım adım oluşturmanıza olanak tanıyan creational bir tasarım desenidir. Kalıp, aynı construction kodunu kullanarak bir nesnenin farklı türlerini ve temsillerini oluşturmanıza olanak tanır.

Daha önceki Tasarım Deseni / Kalıbı / Örüntüsü (Design Pattern) Nedir? başlıklı yazımızda tanıttığımız Creational Patterns altında yer alan Builder Pattern Nedir sorusunu ve kod örneklerini bu yazımızda oldukça detaylı paylaşıyoruz.

Birçok field ve nested nesnelerin zahmetli, adım adım başlatılmasını gerektiren karmaşık bir nesne hayal edin. Bu tür initialization kodu genellikle çok sayıda parametre içeren canavarca bir constructor içindedir. Veya daha da kötüsü: istemci kodunun her yerine dağınık durumdadır.

Bir nesnenin olası her yapılandırması için bir alt sınıf oluşturarak programı çok karmaşık hale getirebilirsiniz.

Örneğin, bir House nesnesinin nasıl oluşturulacağını düşünelim. Basit bir ev inşa etmek için dört duvar ve bir zemin inşa etmeniz, bir kapı takmanız, bir çift pencere takmanız ve bir çatı inşa etmeniz gerekir. Ama ya arka bahçesi ve diğer özellikleri (ısıtma sistemi, sıhhi tesisat ve elektrik tesisatı gibi) olan daha büyük, daha aydınlık bir ev istiyorsanız?

En basit çözüm, temel House sınıfını extend etmek ve tüm parametre kombinasyonlarını kapsayacak bir dizi alt sınıf oluşturmaktır. Ama sonunda çok sayıda alt sınıfa sahip olacaksınız. Sundurma stili gibi herhangi bir yeni parametre, bu hiyerarşiyi daha da büyütmeyi gerektirecektir.

Bu alt sınıfları tek tek üretmeyi içermeyen başka bir yaklaşım daha var. House nesnesini kontrol eden tüm olası parametrelerle temel House sınıfında dev bir constructor oluşturabilirsiniz. Bu yaklaşım aslında alt sınıflara olan ihtiyacı ortadan kaldırırken, başka bir sorun yaratır.

Çok sayıda parametreye sahip constructor’ın dezavantajı vardır: Tüm parametrelere her zaman ihtiyaç duyulmaz.

Çoğu durumda, parametrelerin çoğu kullanılmayacak ve constructor çağrılarını oldukça çirkin hale getirecektir. Örneğin, evlerin sadece bir kısmında yüzme havuzu var, bu nedenle yüzme havuzlarıyla ilgili parametreler onda dokuzunda yararsız olacaktır.

Çözüm

Çözüm olarak Builder (Oluşturucu) desen, nesne oluşturma kodunu kendi sınıfından çıkarmanızı ve onu builder adı verilen ayrı nesnelere taşımanızı önerir.

Builder desen, adım adım karmaşık nesneler oluşturmanıza olanak tanır. Builder, ürün oluşturulurken diğer nesnelerin ürüne erişmesine izin vermez.

Desen, nesne oluşturmayı bir dizi adım (buildWalls, buildDoor, vb.) halinde düzenler. Bir nesne oluşturmak için, bir builder nesnesinde bu adımların bir dizisini uygularsınız. Önemli olan kısım, tüm adımları çağırmanıza gerek olmamasıdır. Yalnızca bir nesnenin belirli bir konfigürasyonunu üretmek için gerekli olan adımları çağırabilirsiniz.

Ürünün çeşitli temsillerini oluşturmanız gerektiğinde, construction adımlarından bazıları farklı uygulama gerektirir. Örneğin, bir kulübenin duvarları ahşap olabilir, ancak kale duvarları taştan yapılmış olmalıdır.

Bu durumda, aynı oluşturma adımlarını farklı bir şekilde uygulayan birkaç farklı builder sınıfı oluşturabilirsiniz. Daha sonra, farklı türde nesneler üretmek için bu builder’leri inşaat sürecinde kullanabilirsiniz (yani, building adımlarına yönelik sıralı bir çağrı seti).

Farklı builders aynı görevi çeşitli şekillerde yürütür.

Örneğin, her şeyi ahşap ve camdan yapan bir builder, her şeyi taş ve demirden inşa eden ikinci bir builder ve altın ve elmas kullanan üçüncü bir builder hayal edin. Aynı adımları çağırarak, ilk builderdan normal bir ev, ikinciden küçük bir kale ve üçüncüden bir saray elde edersiniz. Ancak bu, yalnızca building adımlarını çağıran istemci kodu ortak bir arabirim kullanarak inşaatçılar ile etkileşime girebiliyorsa işe yarar.

Director

Daha ileri gidebilir ve bir ürünü director adlı ayrı bir sınıfa oluşturmak için kullandığınız constructor adımlarına yönelik bir dizi çağrıyı extract edebilirsiniz. Director sınıfı, building adımlarının yürütüleceği sırayı tanımlarken, constructor bu adımlar için uygulamayı sağlar.

Director, çalışan bir ürün elde etmek için hangi building adımlarının uygulanacağını bilir.

Programınızda bir director sınıfının olması kesinlikle gerekli değildir. Building adımlarını her zaman belirli bir sırayla doğrudan müşteri kodundan çağırabilirsiniz. Ancak, director sınıfı, programınızda yeniden kullanabilmeniz için çeşitli construction rutinleri koymak için iyi bir yer olabilir.

Ek olarak, director sınıfı, ürün construction ayrıntılarını müşteri kodundan tamamen gizler. Müşterinin yalnızca bir builder’ı bir müdürle ilişkilendirmesi, inşaata müdürle birlikte başlaması ve inşaatçıdan sonucu alması gerekir.

Structure

Structure
  1. Builder interface’i, her tür builder için ortak olan ürün oluşturma adımlarını bildirir.
  2. Concrete Builder‘lar, building adımlarının farklı uygulamalarını sağlar. Concrete builder’lar, ortak interface’i takip etmeyen ürünler üretir.
  3. Product‘lar sonuç nesneleridir. Farklı builder’lar tarafından oluşturulan product’lar, aynı sınıf hiyerarşisine veya interface’ine ait olmak zorunda değildir.
  4. Director sınıfı, belirli product konfigürasyonlarını oluşturabilmeniz ve yeniden kullanabilmeniz için construction adımlarının çağrılacağı sırayı tanımlar.
  5. Client, builder nesnelerinden birini director ile ilişkilendirmelidir. Genellikle, director’un constructor’ının parametreleri aracılığıyla yalnızca bir kez yapılır. Daha sonra director, diğer tüm construction’lar için bu builder nesnesini kullanır. Ancak, müşterinin builder nesnesini director’un production yöntemine geçirmesi için alternatif bir yaklaşım vardır. Bu durumda, director’le her bir şey ürettiğinizde farklı bir constructor kullanabilirsiniz.

# Builder Pseudocode

Bu Builder desen örneği, arabalar gibi farklı ürün türleri oluştururken aynı nesne construction kodunu nasıl yeniden kullanabileceğinizi ve bunlara karşılık gelen kılavuzları nasıl oluşturabileceğinizi gösterir.

Adım adım araba construction örneği ve bu araba modellerine uyan kullanıcı kılavuzları.

Bir araba, yüzlerce farklı şekilde inşa edilebilen karmaşık bir nesnedir. Car sınıfını büyük bir constructor ile şişirmek yerine, araba montaj kodunu ayrı bir araba builder sınıfına çıkardık. Bu sınıf, bir arabanın çeşitli parçalarını yapılandırmak için bir dizi yönteme sahiptir.

Müşteri kodunun özel, ince ayarlı bir araba modeli oluşturması gerekiyorsa, doğrudan builder ile çalışabilir. Öte yandan, müşteri montajı, en popüler araba modellerinden birkaçını inşa etmek için bir builder’ı nasıl kullanacağını bilen director sınıfına devredebilir.

Şok olabilirsiniz, ancak her arabanın bir kılavuza ihtiyacı vardır. Kılavuz, aracın her özelliğini açıklar, bu nedenle kılavuzlardaki ayrıntılar farklı modellere göre değişir. Bu nedenle, hem gerçek arabalar hem de ilgili kılavuzlar için mevcut bir yapım sürecini yeniden kullanmak mantıklıdır. Elbette, bir kılavuz oluşturmak, bir araba inşa etmekle aynı şey değildir ve bu nedenle, kılavuz oluşturma konusunda uzmanlaşmış başka bir builder sınıfı sağlamalıyız. Bu sınıf, araba yapım kardeşiyle aynı building yöntemlerini implemente eder, ancak araba parçaları üretmek yerine bunları açıklar. Bu builder’ları aynı director nesnesine geçirerek, bir araba ya da bir el kitabı oluşturabiliriz.

Son kısım, ortaya çıkan nesneyi işlemektir. Metal bir araba ve bir kağıt el kitabı, ilgili olmasına rağmen, hala çok farklı şeylerdir. Director’u concrete product sınıflarına bağlamadan director’e sonuç almak için bir yöntem yerleştiremeyiz. Dolayısıyla building sonucunu işi yapan müteahhitten alıyoruz.

// Builder kalıbını kullanmak, yalnızca ürünleriniz oldukça karmaşık olduğunda 
// ve kapsamlı yapılandırma gerektirdiğinde anlamlıdır. Aşağıdaki iki ürün birbiriyle 
// ilişkilidir, ancak ortak bir interface'leri yoktur.
class Car is
    // Bir arabada GPS, yol bilgisayarı ve birkaç koltuk bulunabilir. 
    // Farklı araba modellerinde (spor araba, SUV, cabriolet) farklı özellikler 
    // yüklenmiş veya etkinleştirilmiş olabilir.

class Manual is
    // Her otomobilin, otomobilin konfigürasyonuna uygun ve
    // tüm özelliklerini açıklayan bir kullanım kılavuzu olmalıdır.


// Builder interface'i, ürün nesnelerinin farklı bölümlerini oluşturma yöntemlerini belirtir.
interface Builder is
    method reset()
    method setSeats(...)
    method setEngine(...)
    method setTripComputer(...)
    method setGPS(...)

// Concrete builder sınıfları, builder interface'ini takip eder ve 
// building adımlarının özel implementasyonlarını sağlar. Programınız, 
// her biri farklı şekilde uygulanan çeşitli builder varyasyonlarına sahip olabilir.
class CarBuilder implements Builder is
    private field car:Car

    // Yeni bir builder instance'ı, daha sonraki montajda kullandığı boş bir product nesnesi içermelidir.
    constructor CarBuilder() is
        this.reset()

    // reset methodu, oluşturulmakta olan nesneyi temizler.
    method reset() is
        this.car = new Car()

    // Tüm production adımları aynı ürün instance'ıyla çalışır.
    method setSeats(...) is
        // Arabadaki koltuk sayısını ayarlar

    method setEngine(...) is
        // Verilen bir motoru takar.

    method setTripComputer(...) is
        // Bir yol bilgisayarı kurar.

    method setGPS(...) is
        // Global bir konumlandırma sistemi kurar.

    // Concrete builder'larının sonuçlara ulaşmak için kendi methodlarını sağlamaları beklenir. 
    // Bunun nedeni, çeşitli builder türlerinin hepsi aynı interface'i takip etmeyen tamamen 
    // farklı ürünler yaratabilmesidir. Bu nedenle, bu tür yöntemler builder interface'inde bildirilemez 
    // (en azından statik olarak yazılmış bir programlama dilinde değil).
    //
    // Genellikle, nihai sonucu müşteriye döndürdükten sonra, bir builder instance'ının 
    // başka bir ürün üretmeye başlamaya hazır olması beklenir. Bu nedenle, "getProduct" method 
    // gövdesinin sonunda reset yöntemini çağırmak olağan bir uygulamadır. Ancak, bu davranış zorunlu 
    // değildir ve önceki sonucu elden çıkarmadan önce builder'ınızın istemci kodundan açık bir reset 
    // çağrısını beklemesini sağlayabilirsiniz.
    method getProduct():Car is
        product = this.car
        this.reset()
        return product

// Diğer creational kalıplarından farklı olarak, builder, 
// ortak interface'i takip etmeyen ürünler oluşturmanıza izin verir.
class CarManualBuilder implements Builder is
    private field manual:Manual

    constructor CarManualBuilder() is
        this.reset()

    method reset() is
        this.manual = new Manual()

    method setSeats(...) is
        // araba koltuğu özellikleri belgesi.

    method setEngine(...) is
        // Motor talimatları ekleme.

    method setTripComputer(...) is
        // Yol bilgisayarı talimatlarını ekleme.

    method setGPS(...) is
        // GPS talimatlarını ekleme.

    method getProduct():Manual is
        // Kılavuzu return edin ve builder'i resetleyin.


// Director, yalnızca building adımlarını belirli bir sırayla yürütmekten sorumludur. 
// Belirli bir siparişe veya konfigürasyona göre ürünler üretirken yardımcı olur. 
// Kesin konuşmak gerekirse, müşteri builder'ları doğrudan kontrol edebildiğinden, 
// director sınıfı isteğe bağlıdır.
class Director is
    private field builder:Builder

    // Director, istemci kodunun kendisine ilettiği herhangi bir builder instance'yla çalışır. 
    // Bu şekilde, müşteri kodu yeni monte edilen ürünün son tipini değiştirebilir.
    method setBuilder(builder:Builder)
        this.builder = builder

    // Director, aynı building adımlarını kullanarak birkaç ürün varyasyonu oluşturabilir.
    method constructSportsCar(builder: Builder) is
        builder.reset()
        builder.setSeats(2)
        builder.setEngine(new SportEngine())
        builder.setTripComputer(true)
        builder.setGPS(true)

    method constructSUV(builder: Builder) is
        // ...


// İstemci kodu bir builder nesnesi oluşturur, onu yöneticiye iletir ve 
// ardından construction sürecini başlatır. Nihai sonuç, builder nesnesinden alınır.
class Application is

    method makeCar() is
        director = new Director()

        CarBuilder builder = new CarBuilder()
        director.constructSportsCar(builder)
        Car car = builder.getProduct()

        CarManualBuilder builder = new CarManualBuilder()
        director.constructSportsCar(builder)

        // Nihai ürün, genellikle bir builder nesnesinden alınır, çünkü director, 
        // concrete builderler ve ürünlerden haberdar değildir ve bunlara bağımlı değildir.
        Manual manual = builder.getProduct()

💡 Uygulanabilirlik

🐛 “Telescopic Constructor” dan kurtulmak için Builder desenini kullanın.

⚡ Diyelim ki on isteğe bağlı parametreye sahip bir constructor’ınız var. Böyle bir canavarı çağırmak çok sakıncalıdır; bu nedenle, constructor’ı overload eder ve daha az parametreyle birkaç daha kısa sürüm oluşturursunuz. Bu constructor’lar, bazı varsayılan değerleri atlanan parametrelere aktararak hala ana öğeye başvurur.

class Pizza {
    Pizza(int size) { ... }
    Pizza(int size, boolean cheese) { ... }
    Pizza(int size, boolean cheese, boolean pepperoni) { ... }
    // ...

Builder deseni, yalnızca gerçekten ihtiyacınız olan adımları kullanarak nesneleri adım adım oluşturmanıza olanak tanır. Kalıbı uyguladıktan sonra, artık constructolarınıza düzinelerce parametre sıkıştırmanız gerekmez.

🐛 Composite ağaçlar veya diğer karmaşık nesneler oluşturmak için Builder kullanın.

⚡ Builder deseni, ürünleri adım adım oluşturmanıza olanak tanır. Nihai ürünü bozmadan bazı adımların yürütülmesini erteleyebilirsiniz. Adımları recursive olarak bile çağırabilirsiniz; bu, bir nesne ağacı oluşturmanız gerektiğinde kullanışlıdır.

🗎 Nasıl Implemente Edeceğiz?

  1. Mevcut tüm product temsillerini oluşturmak için ortak construction adımlarını açıkça tanımlayabildiğinizden emin olun. Aksi takdirde, kalıbı uygulamaya devam edemezsiniz.
  2. Bu adımları temel builder interface’inde bildirin.
  3. Product temsillerinin her biri için concrete bir builder sınıfı oluşturun ve construction adımlarını uygulayın. Construction’un sonucunu almak için bir method implemente etmeyi unutmayın. Bu yöntemin builder’in interface’inde bildirilememesinin nedeni, çeşitli builder’lerin ortak bir interface’e sahip olmayan ürünler oluşturabilmesidir. Bu nedenle, böyle bir yöntemin return türünün ne olacağını bilmiyorsunuz. Ancak, tek bir hiyerarşiden ürünlerle uğraşıyorsanız, return yöntemi temel interface’e güvenli bir şekilde eklenebilir.
  4. Bir director sınıfı oluşturmayı düşünün. Aynı builder nesnesini kullanarak bir ürün oluşturmanın çeşitli yollarını içerir.
  5. Client kodu, hem builder hem de director nesneleri oluşturur. Construction başlamadan önce, müşteri bir builder nesnesini director’e iletmelidir. Genellikle, müşteri bunu yönetmenin constructor’ının parametreleri aracılığıyla yalnızca bir kez yapar. Director, tüm diğer constructionlarda builder nesneyi kullanır. Builder’in doğrudan Director’un construciton yöntemine aktarıldığı alternatif bir yaklaşım var.
  6. Construction sonucu, ancak tüm ürünler aynı interface’i takip ederse doğrudan director’den alınır. Aksi takdirde, müşteri sonucu builder’dan almalıdır.

⚖️ Builder Avantajları ve Dezavantajları

  • ✔️ Nesneleri adım adım oluşturabilir, construction adımlarını erteleyebilir veya adımları recursive olarak çalıştırabilirsiniz.
  • ✔️ Ürünlerin çeşitli temsillerini oluştururken aynı construction kodunu yeniden kullanabilirsiniz.
  • <✔️ Single Responsibility Principle. Karmaşık construction kodunu ürünün iş mantığından ayırabilirsiniz.
  • ❌ Desen birden fazla yeni sınıf oluşturmayı gerektirdiğinden, kodun genel karmaşıklığı artar.

⇄ Diğer Desenlerle İlişkisi

  • Birçok tasarım Factory Method (daha az karmaşık ve alt sınıflar aracılığıyla daha özelleştirilebilir) kullanılarak başlar ve Abstract Factory, Prototype veya Builder‘a evrilir (daha esnek, ancak daha karmaşık)
  • Builder, adım adım karmaşık nesneler oluşturmaya odaklanır. Abstract Factory, ilgili nesnelerin ailelerini oluşturma konusunda uzmanlaşmıştır. Abstract Factory ürünü hemen return ederken, Builder ürünü getirmeden önce bazı ek construction adımlarını çalıştırmanıza izin verir.
  • Karmaşık Composite ağaçlar oluştururken Builder‘ı kullanabilirsiniz, çünkü yapım adımlarını recursive çalışacak şekilde programlayabilirsiniz.
  • Builder ile Bridge‘i birleştirebilirsiniz: farklı builder’lar uygulama olarak hareket ederken, director sınıfı soyutlama rolünü oynar.
  • Abstract Factory, Builder ve Prototype’ların tümü Singleton olarak uygulanabilir.

<> Kod Örneği

Builder Example

Aşağıdaki public repo adresine giderek adresine giderek örnek Builder kodlarını inceleyebilirsiniz.

Github repoya gitmek için Tıklayınız.