Builder Pattern
Builder Pattern, karmaşık nesneleri adım adım oluşturmak için kullanılan yaratıcı bir tasarım modelidir. Builder Pattern, uzun bir oluşturucuya veya birçok isteğe bağlı parametreye sahip bir nesne oluşturmak yerine, yapım sürecini nihai nesneden ayırır.
Bu model, bir nesnenin birçok konfigürasyon seçeneği, isteğe bağlı değeri, iç içe geçmiş parçaları veya farklı yapım adımları olduğunda kullanışlıdır. Geliştiricilerin, yapıcıları çok büyük veya kafa karıştırıcı hale getirmeden okunabilir, esnek ve bakımı kolay nesne oluşturma mantığı oluşturmasına yardımcı olur.
Giriş
Bu Design Patterns serisinin önceki makalelerinde Factory Pattern ve Abstract Factory Pattern'yu tartışmıştık. Her iki model de nesne oluşturmaya odaklanır, ancak farklı sorunları çözerler.
Factory Pattern, uygulamanın hangi nesne türünün oluşturulacağına karar vermesi gerektiğinde kullanışlıdır. Abstract Factory Pattern, uygulamanın ilgili nesne aileleri oluşturması gerektiğinde kullanışlıdır. Builder Pattern farklıdır çünkü karmaşık bir nesnenin adım adım nasıl oluşturulacağına odaklanır.
Gerçek yazılım projelerinde bazı nesneler basit bir kurucuyla kolaylıkla oluşturulamaz. Örneğin bir rapor, fatura, e-posta mesajı, kullanıcı profili, HTTP isteği, sorgu nesnesi veya yapılandırma nesnesi birçok isteğe bağlı alan ve farklı kurulum adımları içerebilir. Builder Pattern bu karmaşıklığın yönetilmesine yardımcı olur.
Builder Pattern Nedir?
Builder Pattern, geliştiricilerin karmaşık nesneleri aşamalı olarak oluşturmasına olanak tanıyan yaratıcı bir tasarım modelidir. Bir inşaatçı sınıfına, nesnenin farklı parçalarını ayarlamak için yöntemler sağlar ve inşaat tamamlandığında son nesneyi döndürür.
Basit bir ifadeyle, Builder Pattern, bir nesneyi tek bir büyük adımda oluşturmak yerine, onu birkaç küçük adımla oluşturur.
Örneğin, bir e-posta mesajı oluşturmak için alıcı, konu, gövde, Bilgi listesi, ekler, öncelik ve şablon seçenekleri gerekebilir. Tüm bu değerlerin tek bir kurucuya aktarılması kodun okunmasını zorlaştırabilir. Bir inşaatçı, nesnenin daha anlamlı bir şekilde yaratılmasına olanak tanır.
Builder Pattern Neden Önemlidir?
Builder Pattern önemlidir çünkü nesneler çok sayıda parametreye sahip olduğunda yapıcıların yönetimi zorlaşabilir. Bu soruna genellikle teleskopik yapıcı sorunu denir.
Birçok parametreye sahip bir yapıcının okunması zordur çünkü her değerin ne anlama geldiği her zaman açık değildir. Özellikle birden fazla parametre aynı veri tipine sahip olduğunda, değerlerin yanlış sırada iletilmesi de kolaydır.
Builder Pattern, her yapım adımı için adlandırılmış yöntemleri kullanarak okunabilirliği artırır. Bu, nesne oluşturmayı daha net ve güvenli hale getirir.
Büyük İnşaatçıların Sorunu
Bir kullanıcı profilini temsil eden bir sınıf düşünün. Ad, e-posta, telefon, adres, avatar, biyografi, sosyal bağlantılar, bildirim ayarları, gizlilik ayarları ve dil tercihlerini içerebilir.
Builder Pattern olmadan yapıcı şöyle görünebilir:
$profile = new UserProfile(
'Adnan Mehrat',
'adnan@example.com',
'+905000000000',
'Samsun',
'Turkey',
'Developer',
true,
false,
'en'
);Bu kodu anlamak zordur çünkü her argümanın anlamı açık değildir. Daha sonra daha fazla seçenek eklenirse yapıcının bakımı daha da zorlaşır.
Builder Pattern, inşaat sürecini daha okunabilir hale getirerek bu sorunu çözüyor.
Builder Pattern PHP'daki Temel Örnek
Aşağıdaki örnek, kullanıcı profili oluşturmaya yönelik basit bir Builder Pattern uygulamasını göstermektedir:
class UserProfile
{
public function __construct(
public string $name,
public string $email,
public ?string $phone = null,
public ?string $city = null,
public ?string $country = null,
public ?string $bio = null,
public bool $isPublic = true
) {
}
}
class UserProfileBuilder
{
private string $name;
private string $email;
private ?string $phone = null;
private ?string $city = null;
private ?string $country = null;
private ?string $bio = null;
private bool $isPublic = true;
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
public function setPhone(string $phone): self
{
$this->phone = $phone;
return $this;
}
public function setLocation(string $city, string $country): self
{
$this->city = $city;
$this->country = $country;
return $this;
}
public function setBio(string $bio): self
{
$this->bio = $bio;
return $this;
}
public function makePrivate(): self
{
$this->isPublic = false;
return $this;
}
public function build(): UserProfile
{
return new UserProfile(
$this->name,
$this->email,
$this->phone,
$this->city,
$this->country,
$this->bio,
$this->isPublic
);
}
}Bu örnekte oluşturucu, yapı değerlerini saklar ve yapı yöntemi aracılığıyla son UserProfile nesnesini döndürür.
Builder Pattern'yu kullanma
Builderyu oluşturduktan sonra nesne yapısı daha okunabilir hale gelir:
$profile = (new UserProfileBuilder())
->setName('Adnan Mehrat')
->setEmail('adnan@example.com')
->setPhone('+905000000000')
->setLocation('Samsun', 'Turkey')
->setBio('Backend-focused full stack developer')
->makePrivate()
->build();Bu kodun anlaşılması daha kolaydır çünkü her yöntem adı hangi değerin ayarlandığını açıklar. İnşaat adımları açık ve düzenlidir.
Bu stile genellikle akıcı arayüz denir çünkü her yöntem oluşturucunun kendisini döndürür ve yöntem zincirlemeye izin verir.
Builder Pattern'nun Ana Parçaları
Builder Pattern genellikle aşağıdaki parçaları içerir:
Ürün: Oluşturulan son nesne.
Builder: Nesnenin parçalarını adım adım ayarlamaktan sorumlu sınıf.
Derleme yöntemi: Son nesneyi döndüren yöntem.
Yönetmen: Önceden tanımlanmış bir inşaat sürecini kontrol eden isteğe bağlı bir sınıf.
Her uygulamanın bir Direktöre ihtiyacı yoktur. Birçok PHP uygulamasında basit bir oluşturucu sınıfı yeterlidir.
Builder Pattern Yönetmenle birlikte
Director, ortak yapım sıralarını tanımlayan bir sınıftır. Nesneleri standart bir şekilde oluşturmak için oluşturucuyu kullanır.
Örneğin bir rapor sistemi önceden tanımlanmış farklı rapor türlerine sahip olabilir. Bir yönetici aynı oluşturucuyu kullanarak aylık veya yıllık bir rapor oluşturabilir.
class Report
{
public function __construct(
public string $title,
public string $period,
public array $sections,
public bool $includeCharts
) {
}
}
class ReportBuilder
{
private string $title = '';
private string $period = '';
private array $sections = [];
private bool $includeCharts = false;
public function setTitle(string $title): self
{
$this->title = $title;
return $this;
}
public function setPeriod(string $period): self
{
$this->period = $period;
return $this;
}
public function addSection(string $section): self
{
$this->sections[] = $section;
return $this;
}
public function includeCharts(): self
{
$this->includeCharts = true;
return $this;
}
public function build(): Report
{
return new Report(
$this->title,
$this->period,
$this->sections,
$this->includeCharts
);
}
}
class ReportDirector
{
public function createMonthlyReport(ReportBuilder $builder): Report
{
return $builder
->setTitle('Monthly Sales Report')
->setPeriod('Monthly')
->addSection('Summary')
->addSection('Sales')
->addSection('Expenses')
->includeCharts()
->build();
}
}Bu örnekte ReportDirector, aylık rapor oluşturmanın standart bir yolunu tanımlar. Bu, aynı inşaat süreci birden fazla yerde tekrarlandığında yararlı olabilir.
Yönetmen Yararlı Olduğunda
Director, uygulamanın önceden tanımlanmış nesne oluşturma iş akışlarına sahip olduğu durumlarda kullanışlıdır. Yönetmen aynı inşaat adımlarını birçok yerde tekrarlamak yerine süreci merkezileştirir.
Örneğin bir document sistemi faturalar, sözleşmeler, raporlar ve sertifikalar için standart şablonlara sahip olabilir. Bir yönetici, her document tipini aynı oluşturucuyu kullanarak ancak önceden tanımlanmış farklı adımlarla oluşturabilir.
Ancak nesne oluşturma basitse veya her nesne farklı şekilde oluşturulmuşsa, Direktör kullanmak gereksiz olabilir.
Gerçek Dünyadan Örnek: E-posta Mesajı Builder
Bir e-posta mesajı Builder Pattern'nun pratik bir örneğidir. E-postalarda CC, BCC, ekler, öncelik, şablon ve meta veriler gibi birçok isteğe bağlı bölüm bulunabilir.
class EmailMessage
{
public function __construct(
public string $to,
public string $subject,
public string $body,
public array $cc = [],
public array $bcc = [],
public array $attachments = [],
public string $priority = 'normal'
) {
}
}
class EmailMessageBuilder
{
private string $to;
private string $subject;
private string $body;
private array $cc = [];
private array $bcc = [];
private array $attachments = [];
private string $priority = 'normal';
public function to(string $email): self
{
$this->to = $email;
return $this;
}
public function subject(string $subject): self
{
$this->subject = $subject;
return $this;
}
public function body(string $body): self
{
$this->body = $body;
return $this;
}
public function cc(string $email): self
{
$this->cc[] = $email;
return $this;
}
public function bcc(string $email): self
{
$this->bcc[] = $email;
return $this;
}
public function attach(string $filePath): self
{
$this->attachments[] = $filePath;
return $this;
}
public function highPriority(): self
{
$this->priority = 'high';
return $this;
}
public function build(): EmailMessage
{
return new EmailMessage(
$this->to,
$this->subject,
$this->body,
$this->cc,
$this->bcc,
$this->attachments,
$this->priority
);
}
}Bu oluşturucu, e-posta oluşturmayı anlaşılır ve esnek hale getirir.
E-posta Builderyu Kullanma
$email = (new EmailMessageBuilder())
->to('user@example.com')
->subject('Welcome to our platform')
->body('Thank you for joining us.')
->cc('manager@example.com')
->attach('/files/welcome.pdf')
->highPriority()
->build();Bu yaklaşım, birçok diziyi ve isteğe bağlı parametreyi tek bir kurucuya aktarmaktan daha okunabilirdir.
Builder Pattern ve Değiştirilemez Nesneler
Builder Pattern, değişmez nesnelerle iyi çalışır. Değişmez bir nesne, oluşturulduktan sonra durumu değiştirilemeyen bir nesnedir.
Bu tasarımda inşaatı yapan kişi inşaat sırasında değişebilir ancak nihai ürün değişmez. Bu, nesnenin oluşturulduktan sonra beklenmeyen değişiklikler olmadan güvenle kullanılabileceği anlamına gelir.
Örneğin yapılandırma nesneleri, değer nesneleri, istek nesneleri ve DTOs bu yaklaşımdan yararlanabilir. Builder değerleri toplar, bunları doğrular ve ardından kararlı bir son nesne oluşturur.
Builder İçinde Doğrulama
Bir oluşturucu, son nesneyi oluşturmadan önce gerekli değerleri de doğrulayabilir. Bu, eksik veya geçersiz nesnelerin oluşturulmasını engeller.
public function build(): EmailMessage
{
if (empty($this->to)) {
throw new InvalidArgumentException('Recipient email is required.');
}
if (empty($this->subject)) {
throw new InvalidArgumentException('Email subject is required.');
}
if (empty($this->body)) {
throw new InvalidArgumentException('Email body is required.');
}
return new EmailMessage(
$this->to,
$this->subject,
$this->body,
$this->cc,
$this->bcc,
$this->attachments,
$this->priority
);
}Bu, nesnenin uygulama tarafından kullanılmadan önce eksiksiz ve geçerli olmasını sağlar.
Builder Pattern vs Factory Pattern
Builder Pattern ve Factory Pattern'nun her ikisi de yaratıcı modellerdir, ancak farklı sorunları çözerler.
Factory Pattern hangi nesnenin oluşturulacağını seçmeye odaklanır. Örneğin, bir PaymentFactory, CreditCardPayment'in mi yoksa PayPalPayment'in mi oluşturulacağına karar verebilir.
Builder Pattern, karmaşık bir nesnenin adım adım nasıl oluşturulacağına odaklanır. Örneğin, bir EmailMessageBuilder, son EmailMessage'ı oluşturmadan önce alıcıyı, konuyu, metni, CC'yi, BCC'yi, ekleri ve önceliği ayarlayabilir.
Kısaca Factory “hangi nesne oluşturulmalı?” sorusunu yanıtlıyor. Builder ise "Bu karmaşık nesne nasıl inşa edilmeli?" sorusunu yanıtlıyor.
Builder Pattern vs Abstract Factory Pattern
Abstract Factory Pattern ilgili nesnelerin ailelerini oluşturur. Builder Pattern, birden çok adımla tek bir karmaşık nesne oluşturur.
Örneğin, Abstract Factory, karanlık bir tema için Button, Giriş ve Modal gibi bir grup kullanıcı arayüzü bileşeni oluşturabilir. Builder, başlık, düzen, bölümler, meta veriler ve ayarları içeren karmaşık bir Sayfa nesnesi oluşturabilir.
Her iki model de daha iyi nesne oluşturulmasını destekler ancak amaçları farklıdır.
Laravel'da Builder Pattern
Laravel geliştiricileri, oluşturucu stili APIs'yu sıklıkla kullanıyor. Örneğin, Laravel'nun sorgu oluşturucusu, geliştiricilerin yöntem zincirlemeyi kullanarak adım adım database sorguları oluşturmasına olanak tanır.
$users = DB::table('users')
->where('active', true)
->orderBy('created_at', 'desc')
->limit(10)
->get();Bu stil Builder Pattern'ya benzer çünkü sorgu yürütmeden önce aşamalı olarak oluşturulur.
Geliştiriciler ayrıca Laravel uygulamalarındaki raporlar, filtreler, search sorguları, yapılandırmayı dışa aktarma, API istekleri veya karmaşık DTO nesneleri için özel oluşturucular oluşturabilir.
Symfony'da Builder Pattern
Symfony uygulamaları, karmaşık nesne oluşturmak için oluşturucu sınıflarını da kullanabilir. Örneğin, oluşturucular form yapılandırmalarının oluşturulmasına, yüklerin talep edilmesine, mesaj nesnelerinin veya rapor aktarımlarının oluşturulmasına yardımcı olabilir.
Bağımlılık eklemeyle birleştirildiğinde oluşturucu sınıfları temiz ve yeniden kullanılabilir kalabilir. Ayrıca denetleyicilerden ve hizmetlerden ayrı olarak da test edilebilirler.
Builder Pattern'nun Gerçek Kullanım Durumları
Builder Pattern birçok gerçek yazılım geliştirme senaryosunda kullanışlıdır.
Yaygın kullanım durumları şunları içerir:
İsteğe bağlı CC, BCC ve eklerle e-posta mesajları oluşturma.
Bölümler, grafikler, filtreler ve dışa aktarma seçenekleriyle raporlar oluşturma.
Başlıklar, sorgu parametreleri ve gövde verileriyle HTTP istekleri oluşturma.
Karmaşık DTOs veya değer nesneleri oluşturma.
search filtreleri veya sorgu nesneleri oluşturma.
Öğeler, vergiler, indirimler ve meta veriler içeren fatura nesneleri oluşturma.
Birçok isteğe bağlı ayarla yapılandırma nesneleri oluşturma.
Tüm bu durumlarda Builder Pattern okunabilirliği artırır ve nesne oluşturma işlemini düzenli tutar.
Builder Pattern'nun Faydaları
Builder Pattern, bir nesnenin birçok isteğe bağlı veya karmaşık yapım adımına sahip olması durumunda birçok fayda sağlar.
Ana faydalar şunları içerir:
Nesne oluşturma kodunun okunabilirliğini artırır.
Birçok parametreye sahip uzun yapıcılardan kaçınılır.
Adım adım nesne oluşumunu destekler.
İsteğe bağlı değerlerin yönetimini kolaylaştırır.
Nesneyi oluşturmadan önce gerekli değerleri doğrulayabilir.
Değişmez nesnelerle iyi çalışır.
Akıcı arayüzleri ve yöntem zincirlemeyi destekler.
Yapım mantığını nihai nesneden ayırır.
Bu avantajlar Builder Pattern'yu karmaşık nesne oluşturma açısından çok pratik hale getirir.
Builder Pattern'nun dezavantajları
Builder Pattern kullanışlı olmasına rağmen dezavantajları da vardır. Ekstra sınıflar ve daha fazla kod ekleyebilir. Basit nesneler için bu ekstra yapı gereksiz olabilir.
Bir nesnenin yalnızca iki veya üç gerekli parametresi varsa, normal bir kurucu, kurucudan daha anlaşılır olabilir. Her sınıf için bir inşaatçı eklemek projeyi daha karmaşık hale getirebilir.
Diğer bir dezavantaj, kötü tasarlanmış bir oluşturucunun geçersiz ara durumlara izin verebilmesidir. Gerekli değerler mevcut olduğunda derleme yöntemindeki validation'nun önemli olmasının nedeni budur.
Builder Pattern Ne Zaman Kullanılır?
Nesne oluşturmanın karmaşık, isteğe bağlı olduğu veya normal bir oluşturucuyla okunmasının zor olduğu durumlarda Builder Pattern'yu kullanın.
Builder Pattern şu durumlarda faydalıdır:
Nesnenin birçok yapıcı parametresi vardır.
Birçok parametre isteğe bağlıdır.
Birkaç değer aynı veri türüne sahiptir ve karıştırılabilir.
Nesne adım adım oluşturulmalıdır.
Nihai nesne değişmez olmalıdır.
İnşaat sürecinin validation'ya ihtiyacı var.
Kod, yöntem zincirlemeyle daha okunabilir olmalıdır.
Bu koşullar mevcutsa Builder Pattern kod kalitesini önemli ölçüde artırabilir.
Builder Pattern Ne Zaman Kullanılmamalı?
Nesne oluşturmanın basit olduğu durumlarda Builder Pattern kullanmayın. Bir sınıfın yalnızca birkaç gerekli değeri varsa ve karmaşık bir kurulumu yoksa normal bir kurucu genellikle daha iyidir.
Aşağıdaki durumlarda Builder Pattern'dan kaçının:
Nesnenin çok az parametresi var.
Yapıcı zaten açık ve okunabilir.
Builder yalnızca yapıcı mantığını kopyalar.
Projenin adım adım inşaata ihtiyacı yoktur.
Desen, değerden çok karmaşıklık katar.
Design Patterns gerçek sorunları çözmelidir. Yalnızca kodun daha gelişmiş görünmesi için eklenmemelidirler.
Builder Pattern ve Temiz Kod
Builder Pattern, nesne oluşturmayı daha etkileyici hale getirerek clean code'yu destekler. Belirsiz değerleri uzun bir kurucuya aktarmak yerine, her oluşturucu yöntemi değerin amacını açıklar.
Bu, karışıklığı azaltır ve diğer geliştiricilerin kodu okumasını kolaylaştırır. Ayrıca inşaat mantığını tek bir yerde tutar, bu da sürdürülebilirliği artırır.
Ancak clean code aynı zamanda gereksiz karmaşıklıktan kaçınmak anlamına da gelir. İnşaatçılar yalnızca inşaatı daha anlaşılır hale getirdiklerinde kullanılmalıdır.
Builder Pattern ile Yapılan Yaygın Hatalar
Yaygın bir hata, yapıcılar basit olsa bile her nesne için Builder Pattern kullanılmasıdır. Bu çok fazla gereksiz sınıf yaratır.
Diğer bir hata da nesneyi oluşturmadan önce gerekli değerleri doğrulamamaktır. Bu, tamamlanmamış nesnelerin oluşturulmasına izin verebilir.
Üçüncü bir hata, iş iş akışlarını oluşturucunun içine yerleştirmektir. Bir inşaatçı iş operasyonlarını yürütmeye değil, nesne inşa etmeye odaklanmalıdır.
Dördüncü hata, inşaatçı yöntemlerini belirsiz veya tutarsız hale getirmektir. Yöntem adları yapım adımını açıkça tanımlamalıdır.
Builder Pattern için En İyi Uygulamalar
Builder Pattern'yu etkili bir şekilde kullanmak için geliştiricilerin inşaatçının inşaata odaklanmasını sağlaması ve API'yu okunabilir hale getirmesi gerekir.
Yararlı en iyi uygulamalar şunları içerir:
Basit nesneler için değil, karmaşık nesneler için oluşturucuları kullanın.
Her yapım adımını açıklayan anlaşılır yöntem adları kullanın.
Yöntem zincirlemeyi desteklemek için oluşturucu yöntemlerinden kendini döndür.
Derleme yöntemi içindeki gerekli değerleri doğrulayın.
İş mantığını oluşturucunun dışında tutun.
Uygun olduğunda değişmez son nesnelere sahip inşaatçıları kullanın.
Gereksiz oluşturucu sınıfları oluşturmaktan kaçının.
Nihai ürün nesnesini odaklanmış ve temiz tutun.
Bu uygulamalar, geliştiricilerin Builder Pattern'yu pratik ve bakımı kolay bir şekilde uygulamalarına yardımcı olur.
Builder Pattern Kullanmadan Önce Pratik Kontrol Listesi
Builder Pattern'yu kullanmadan önce geliştiriciler şu soruları sorabilir:
Nesnenin birçok parametresi var mı?
Birçok parametre isteğe bağlı mı?
Yapıcının okunması zor mu?
Değerler benzer türlere sahip oldukları için karıştırılabilir mi?
Nesnenin adım adım inşa edilmesi gerekiyor mu?
Nihai nesne değişmez mi olmalı?
Bir inşaatçı kodu daha net hale getirecek mi?
Bu soruların birçoğuna cevabınız evet ise Builder Pattern iyi bir seçim olabilir.
Sonuç
Builder Pattern, karmaşık nesneleri adım adım oluşturmak için kullanılan yaratıcı bir tasarım modelidir. Bir nesnenin birçok isteğe bağlı parametresi, iç içe geçmiş değeri, yapılandırma seçeneği veya validation kuralı varsa bu özellikle kullanışlıdır.
Builder Pattern, uzun oluşturucuları anlaşılır yöntem çağrılarıyla değiştirerek okunabilirliği artırır. Yapım mantığını nihai nesneden ayırır ve değişmez nesnelerle, DTOs, raporlar, e-posta iletileri, HTTP istekleri ve yapılandırma nesneleriyle iyi çalışır.
Ancak Builder Pattern yalnızca gerçek bir karmaşıklık sorununu çözdüğünde kullanılmalıdır. Basit nesnelerin inşaatçılara ihtiyacı yoktur. Doğru kullanıldığında Builder Pattern temiz, okunabilir ve bakımı yapılabilir nesne yönelimli yazılım yazmak için güçlü bir araçtır.

