Design Patterns & Golang: Builder Pattern

Arda Coşar
5 min readJun 20, 2023

--

Giriş

Selamlar, design pattern yazı serimde özellikle gerçek hayattan basit örnekler seçmeye çalışıyorum. Çünkü sektörün içinden olsun diye seçilen karmaşık örnekler, hakim olmadığınız terimleri barındırabileceğinden ana fikrin anlaşılmasını zorlaştırabiliyor. Ayrıca sektördeki kullanım alanlarını vermenin kişiyi, pattern’i anlamaktan çok ezbere ittiğini düşünüyorum.

Hedefim, patternleri kullanmanız gerektiği yerlerde problemi tanımanız ve çözümü kendi şartlarınıza göre uyarlayabilecek seviyeye gelmeniz olacak.

Hikaye

Bugün dokuz yaşına giriyorsun ve hatırlıyorsun ki baban sana bir bilgisayar sözü vermişti. Hevesle onu darlamaya gidiyorsun ama bir sorun var. Ne alacağını bilmiyorsun. Baban sana karar verdiğinde gideriz diyor ve gönderiyor. Ufak adımlarla odana gidip araştırmaya başlıyorsun.

Bir süre sonra bunun içinden çıkılamaz bir hal aldığını fark ediyorsun. Sen sadece istediğin oyunları kaldıracak bir bilgisayar istiyordun. Sana gereken donanımın ayrıntılarını bilmiyorsun. Çaresizce babana gidip oradan bir abiye sorarız diyerek ikna ediyorsun.

Mağazaya geldiğinde usul usul gezerken 5. günün şafağında doğudan gelen gandalf misali top sakallı birisi yanına doğru yaklaşıyor. Diyor ki ufaklık sen ne istiyorsun. Abim diyorsun, canım abim “Ben oyun bilgisayarı istiyorum”. O anda top sakallı abinin sana tatlı tatlı gülümsemesiyle hallederiz lafının verdiği güven hissine kendini teslim ediyorsun. Geri kalan ayrıntılarla top sakallı abin uğraşıyor ve sana ihtiyacın olan PC’yi paketleyip veriyor. Baban da muhtemelen 1 yıllık birikimini masaya bırakıyor. Sen mutlusun, baban kederli…

Problem

  • İstenilen PC için seçilmesi gereken parçaların bilinmemesi
  • Parçaların nasıl birleştirileceğinin bilinmemesi

Çözüm

  • Katalogda önceden hazırlanmış kasalardan seçim yapılması
  • PC’nin toplanmasının top sakallı abiye bırakılması

Teknik Giriş

Bu patternin tekniğine girmeden önce bir şeye değinmek istiyorum. Çoğu yerde builder pattern’i biraz daha farklı görebilirsiniz. Genelde immutable nesnelerin dinamik yaratılabilmesi için kullanılırlar. Bu nesneler oluşturulurken constructor‘a verilen parametreler üzerinden şekil alır. Ancak bazen nesnenin bütün özelliklerini girmek istemeyebilirsiniz. Bu durumda her türlü varyasyon için uygun constructor yazılması gerekiyor. Çünkü hep aynı constructor metodunu kullandığınızda, eklemek istemediğiniz veriler için parametreleri boş geçmeye başlayacaksınız. İşte bu zorunluluktan kurtulmak için builder pattern kullanarak sadece istenilen verilerden oluşan nesneler yaratabilirsiniz.

Golang OOP gereksinimlerini tam olarak karşılayamayan bir dil olduğu için farklı bir yaklaşımı irdeleyeceğiz. Biz kullanıcının tüm ayrıntılarını bilmek zorunda olmadığı, sadece istediği türü söyleyerek detaylı bir nesne elde edebildiği dizaynı uygulayacağız. Bu bilgilerin ışığında artık hikayeyi modellemeye geçebiliriz.

İş’e hikayemizin odak noktası olan Bilgisayar objesini yaratmakla başlayalım.

type Bilgisayar struct {
islemci string
ekranKarti string
ram string
depolama string
anakart string
}

Bilgisayarı temsil edebilecek oldukça yeterli bir yapı oluşturmuş olduk. Sıradaki hedefimiz ise PC toplamak için bize yol gösterecek olan PCSetup arayüzünü tanımlamak olacaktır.

type PCSetup interface {
islemciEkle()
ekranKartiEkle()
ramEkle()
depolamaEkle()
anakartEkle()
parcalariBirlestir() Bilgisayar
}

Artık bize bilgisayarın nasıl toplanacağı hakkında rehberlik edebilecek bir arayüzümüz var. Şimdi de bu arayüzü uygulayan belli başlı özelliklerde PC objelerini tanımlayalım.

type DusukKalitePC struct {
islemci string
ekranKarti string
ram string
depolama string
anakart string
}

func (pc *DusukKalitePC) islemciEkle() {
pc.islemci = "Düşük kalite İşlemci"
}

func (pc *DusukKalitePC) ekranKartiEkle() {
pc.ekranKarti = "Düşük kalite Ekran Kartı"
}

func (pc *DusukKalitePC) ramEkle() {
pc.ram = "Düşük kalite Ram"
}

func (pc *DusukKalitePC) depolamaEkle() {
pc.depolama = "Düşük kalite Depolama"
}

func (pc *DusukKalitePC) anakartEkle() {
pc.anakart = "Düşük kalite Anakart"
}

func (pc *DusukKalitePC) parcalariBirlestir() Bilgisayar {
return Bilgisayar{
islemci: pc.islemci,
ekranKarti: pc.ekranKarti,
ram: pc.ram,
depolama: pc.depolama,
anakart: pc.anakart,
}
}
type OrtaKalitePC struct {
islemci string
ekranKarti string
ram string
depolama string
anakart string
}

func (pc *OrtaKalitePC) islemciEkle() {
pc.islemci = "Orta kalite İşlemci"
}

func (pc *OrtaKalitePC) ekranKartiEkle() {
pc.ekranKarti = "Orta kalite Ekran Kartı"
}

func (pc *OrtaKalitePC) ramEkle() {
pc.ram = "Orta kalite Ram"
}

func (pc *OrtaKalitePC) depolamaEkle() {
pc.depolama = "Orta kalite Depolama"
}

func (pc *OrtaKalitePC) anakartEkle() {
pc.anakart = "Orta kalite Anakart"
}

func (pc *OrtaKalitePC) parcalariBirlestir() Bilgisayar {
return Bilgisayar{
islemci: pc.islemci,
ekranKarti: pc.ekranKarti,
ram: pc.ram,
depolama: pc.depolama,
anakart: pc.anakart,
}
}
type YuksekKalitePC struct {
islemci string
ekranKarti string
ram string
depolama string
anakart string
}

func (pc *YuksekKalitePC) islemciEkle() {
pc.islemci = "Yüksek kalite İşlemci"
}

func (pc *YuksekKalitePC) ekranKartiEkle() {
pc.ekranKarti = "Yüksek kalite Ekran Kartı"
}

func (pc *YuksekKalitePC) ramEkle() {
pc.ram = "Yüksek kalite Ram"
}

func (pc *YuksekKalitePC) depolamaEkle() {
pc.depolama = "Yüksek kalite Depolama"
}

func (pc *YuksekKalitePC) parcalariBirlestir() Bilgisayar {
return Bilgisayar{
islemci: pc.islemci,
ekranKarti: pc.ekranKarti,
ram: pc.ram,
depolama: pc.depolama,
anakart: pc.anakart,
}
}

Örnekelerde görebileceğiniz üzere oldukça anlaşılır fonksiyonlar barındırıyor. Bu PC örneklerinden birini verdiğimizde bize bilgisayarı toplayıp teslim edebilecek bir yiğit lazım. Onu da Personel objemiz ile hallediyoruz.

type Personel struct {
pcSetup PCSetup
}

func (d *Personel) SecilenKasa(b PCSetup) {
d.pcSetup = b
}

func (d *Personel) KasayiTopla() Bilgisayar {
d.pcSetup.islemciEkle()
d.pcSetup.ekranKartiEkle()
d.pcSetup.ramEkle()
d.pcSetup.depolamaEkle()
d.pcSetup.anakartEkle()
return d.pcSetup.parcalariBirlestir()
}

Personel objesi kendisine ne verirseniz onu topluyor ve size teslim ediyor. Bu noktada hangi bilgisayarı, hangi isteklere göre seçebileceğinizi tanımladığımız yardımcı son bir fonksiyona daha ihtiyacımız var.

func PCSetupSecimi(istek string) PCSetup {
switch istek {
case "Oyun oynamak", "İş yapmak":
return &YuksekKalitePC{}
case "Hobi":
return &OrtaKalitePC{}
case "Film izlemek", "Dizi izlemek":
return &DusukKalitePC{}
}

return nil
}

Bu fonksiyonun yaptığı tek şey isteğimiz olan durumlara karşılık bize uygun olan PC’nin tarifini içeren objeyi dönmesidir. Buradan aldığım bilgiyi personele verdiğimde ihtiyacım olan bilgisayarın hazırlanmasını talep etmiş olacağız.

Artık ana koda geçmeye hazırız. Beraber inceleyeceğimiz örnek kod bloğuna geçtiğimizde her şey daha da netleşecektir.

func OrnekCalistir() {
var (
PC Bilgisayar
topSakalReis Personel
)

fmt.Println("Oyun oynamak için PC tercihi yapılıyor...")
oyunPc := PCSetupSecimi("Oyun oynamak")

topSakalReis.SecilenKasa(oyunPc)
PC = topSakalReis.KasayiTopla()
fmt.Printf("İşlemci: %s\n", PC.islemci)
fmt.Printf("Ekran Kartı: %s\n", PC.ekranKarti)
fmt.Printf("Ram: %s\n", PC.ram)
fmt.Printf("Depolama: %s\n", PC.depolama)
fmt.Printf("Anakart: %s\n", PC.anakart)

fmt.Println("------------------------")

fmt.Println("Dizi izlemek için PC tercihi yapılıyor...")
diziPc := PCSetupSecimi("Dizi izlemek")

topSakalReis.SecilenKasa(diziPc)
PC = topSakalReis.KasayiTopla()
fmt.Printf("İşlemci: %s\n", PC.islemci)
fmt.Printf("Ekran Kartı: %s\n", PC.ekranKarti)
fmt.Printf("Ram: %s\n", PC.ram)
fmt.Printf("Depolama: %s\n", PC.depolama)
fmt.Printf("Anakart: %s\n", PC.anakart)
}

Örneğimizde PC seçimi yaparak başlıyorum. Ben sadece Oyun oynamak için bir bilgisayara ihtiyacım olduğunu belirtiyorum. Bir nevi katalog üzerinden kendinize uygun kasa seçtiğinizi de düşünebilirsiniz. Bunu nasıl hazırlayacağınızı bilmenize de gerek yok. Çünkü bu iş için topSakalReis size yardımcı olacaktır. Kendisine verdiğim objeye göre bana uygun olan bilgisayarı toplayıp getiriyor. Daha sonrasında log üzerinden inceleyerek doğru geldiğinden emin olabiliriz.

Aynı örneği Dizi izlemek seçeneği ile tekrar uyguluyorum. Bunun için çok da güçlü bir pc’ye gerek olmadığı için ona göre bir sonuç dönüyor. Artık çıktıları kontrol edebiliriz:

Oyun oynamak için PC tercihi yapılıyor…
İşlemci: Yüksek kalite İşlemci
Ekran Kartı: Yüksek kalite Ekran Kartı
Ram: Yüksek kalite Ram
Depolama: Yüksek kalite Depolama
Anakart: Yüksek kalite Anakart
— — — — — — — — — — — —
Dizi izlemek için PC tercihi yapılıyor…
İşlemci: Düşük kalite İşlemci
Ekran Kartı: Düşük kalite Ekran Kartı
Ram: Düşük kalite Ram
Depolama: Düşük kalite Depolama
Anakart: Düşük kalite Anakart

Sonuç

Artık bu dizaynın ne gibi problemlere çözüm getirdiğini biliyorsunuz. Karmaşık objelerle uğraştığınız noktada bunları çeşit çeşit builder objeleriyle gerçekleştirebilirsiniz. Ya da girişte bahsettiğim, sektörde sık kullanımı olan dinamik obje yaratımı için kullanabilirsiniz. Her halükarda builder pattern oldukça işe yarar ve kullanışlı bir tasarım olarak gönüllerde yer alıyor.

--

--