Creational — Oluşturucu Desenler
Builder
İnşaatçı (Kurucu) Deseni
Amaç: Karmaşık bir nesnenin adım adım oluşturulmasını sağlamak; aynı oluşturma süreciyle farklı görünümlerde nesneler üretebilmek.
Basit Anlatım
Teknik terim yok — günlük hayattan bir benzetme
Bir burger dükkânında sipariş verdiğinizi düşünün. Garson size tek seferde "Ekmek, köfte, peynir, marul, domates, turşu, soğan, sos istiyor musunuz, evet mi hayır mı?" diye 10 soruyu aynı anda sormaz.
Bunun yerine adım adım ilerlersiniz: "Ekmek tam buğday olsun. Köfte duble. Peynir ekle. Soğan istemiyorum." Sonunda "Tamam, hazırla" dersiniz ve burgeriniz gelir.
Bir de "usta" var
Menüde hazır seçenekler de var: "Klasik Burger" dediğinizde usta, aynı adımları her seferinde aynı sırayla uygular. Siz adımları tek tek söylemek zorunda kalmazsınız.
Bir ev inşaatı da böyledir: önce temel, sonra duvarlar, sonra çatı, en son boya. Havuzlu da yapabilirsiniz, havuzsuz da. Aynı süreç, farklı sonuçlar.
Teknik Anlamı
Bir yazılımcının bakış açısıyla
Builder, nesne oluşturma kodunu nesnenin kendisinden ayırarak ayrı bir builder nesnesine taşır. Nesne, bir dizi adım (metot çağrısı) ile yapılandırılır ve en sonda Build() ile elde edilir.
Çözdüğü problem: Teleskopik constructor
new Pizza("büyük", true, false, true, null, 2, false) gibi bir çağrıda hangi parametrenin ne olduğunu anlamak imkânsızdır. Opsiyonel parametre sayısı arttıkça constructor'lar çoğalır ve okunabilirlik çöker.
Katılımcılar
- Builder: Oluşturma adımlarını tanımlar.
- ConcreteBuilder: Adımları uygular ve sonucu döndürür.
- Product: Oluşturulan karmaşık nesne.
- Director (opsiyonel): Adımları belirli bir sırayla çağırarak hazır konfigürasyonlar ("tarifler") üretir.
.NET'ten tanıdık örnekler
StringBuilder,UriBuilder,SqlConnectionStringBuilder- ASP.NET Core'daki
WebApplication.CreateBuilder(args)veHostBuilder
Avantajlar
- Okunabilir, akıcı (fluent) API:
.WithX().WithY().Build() Build()içinde doğrulama yapılarak geçersiz nesne oluşması engellenir.- Ürün immutable (değiştirilemez) tasarlanabilir.
Dezavantajlar
- Ek sınıf ve kod gerektirir; basit nesneler için aşırıdır.
C# ile Uygulama
Adım adım, çalışan kod örneği
Senaryo: Bir e-posta mesajı oluşturuyoruz. Alıcı ve konu zorunlu; CC, ek, öncelik gibi alanlar opsiyonel.
Adım 1 Product: değiştirilemez (immutable) Email sınıfı
Constructor internal olduğu için dışarıdan ancak builder ile oluşturulabilir.
public class Email
{
public string To { get; }
public string Subject { get; }
public string Body { get; }
public IReadOnlyList<string> Cc { get; }
public IReadOnlyList<string> Attachments { get; }
public bool IsUrgent { get; }
internal Email(string to, string subject, string body, List<string> cc,
List<string> attachments, bool isUrgent)
{
To = to; Subject = subject; Body = body;
Cc = cc; Attachments = attachments; IsUrgent = isUrgent;
}
public override string ToString() =>
$"Kime: {To} | CC: {string.Join(", ", Cc)} | Konu: {Subject}" +
$"{(IsUrgent ? " [ACİL]" : "")} | Ekler: {Attachments.Count} adet";
}Adım 2 Fluent Builder
Her metot this döndürdüğü için çağrılar zincirlenebilir. Build() son kontrolleri yapar.
public class EmailBuilder
{
private string? _to;
private string? _subject;
private string _body = "";
private readonly List<string> _cc = new();
private readonly List<string> _attachments = new();
private bool _urgent;
public EmailBuilder To(string address) { _to = address; return this; }
public EmailBuilder Subject(string subject) { _subject = subject; return this; }
public EmailBuilder Body(string body) { _body = body; return this; }
public EmailBuilder Cc(string address) { _cc.Add(address); return this; }
public EmailBuilder Attach(string file) { _attachments.Add(file); return this; }
public EmailBuilder Urgent() { _urgent = true; return this; }
public Email Build()
{
if (string.IsNullOrWhiteSpace(_to))
throw new InvalidOperationException("Alıcı zorunludur.");
if (string.IsNullOrWhiteSpace(_subject))
throw new InvalidOperationException("Konu zorunludur.");
return new Email(_to, _subject, _body, new(_cc), new(_attachments), _urgent);
}
}Adım 3 Director: hazır tarifler
Sık kullanılan konfigürasyonları tek bir yerde toplar.
public static class EmailDirector
{
public static Email MonthlyReport(string manager) =>
new EmailBuilder()
.To(manager)
.Cc("finans@firma.com")
.Subject("Aylık Rapor")
.Body("Rapor ektedir.")
.Attach("rapor.xlsx")
.Build();
}Adım 4 Kullanım
Tüm parçaları bir araya getiren istemci (client) kodu:
var email = new EmailBuilder()
.To("ayse@firma.com")
.Subject("Sunucu çöktü")
.Cc("devops@firma.com")
.Cc("cto@firma.com")
.Attach("log.txt")
.Urgent()
.Build();
Console.WriteLine(email);
Console.WriteLine(EmailDirector.MonthlyReport("mehmet@firma.com"));
try
{
new EmailBuilder().Subject("Alıcısız mesaj").Build();
}
catch (InvalidOperationException ex)
{
Console.WriteLine($"Hata: {ex.Message}");
}Konsol çıktısı
Kime: ayse@firma.com | CC: devops@firma.com, cto@firma.com | Konu: Sunucu çöktü [ACİL] | Ekler: 1 adet Kime: mehmet@firma.com | CC: finans@firma.com | Konu: Aylık Rapor | Ekler: 1 adet Hata: Alıcı zorunludur.
Kodda dikkat edilmesi gerekenler
- Kod kendi kendini belgeliyor:
.Urgent()okununca ne yaptığı belli;new Email(..., true)ise belli değil. - Doğrulama
Build()içinde olduğu için eksik alanlı birEmailnesnesi asla oluşamaz. - C#'ta basit durumlar için
requiredproperty'ler ve object initializer (new Email { To = ... }) de bir alternatiftir; Builder, adımların ve doğrulamanın karmaşık olduğu durumlarda öne çıkar.