Design Patterns & Golang: Decorator Pattern

Arda Coşar
4 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

Çok sevdiğiniz bir şişeniz var. Tam bir kampçısınız ve hep bu şişeyi kullanıyorsunuz. Ama bir gün temiz su kaynağının olmadığı bir ormanda artık canınıza tak etti.

Kirli suyu filtreleyebilen bir şişeye geçmeliydiniz. Oysa kendi şişenizi de kullanmaya devam etmek istiyorsunuz. Bazen buz gibi ve tertemiz şelale sularını şişenize dolduruyorsunuz ve bunun da filtreden geçmesini istemiyorsunuz. Yanınızda 2 şişe taşıyabilirsiniz ama bu da saçma ve biraz da maliyetli geliyor.

Araştırma yaparken şişenize entegre edebileceğiniz tak-çıkar bir filtreleme ürünü görüyorsunuz. Bu sayede hem kendi şişenizi normal bir şekilde kullanabilecek, hem de kirli su gördüğünüzde bu aparatı takıp filtreleyerek temiz su elde edebileceksiniz. Denediğinizde kirli sudan arındığını ancak tadının çok da hoş olmadığını farkettiniz. Neyseki sitede Aroma verici bir aparat daha görmüştünüz. Onu da şişenize taktınız ve artık şişenizden içtiğiniz su hem temiz hem de tadı güzel olarak dilinize değiyor. İstediğiniz zaman da şişenizi eskisi gibi kullanmaya devam edebiliyorsunuz.

Problem

  • Sevilen şişenin kullanılmaya devam edilmek istenmesi ama kirli su kaynaklarında yetersiz kalması
  • Hem sevdiğiniz şişenin hem de filtreli şişenin taşınmasının gereksiz ve maliyetli olması
  • Suya aroma katmak istersem de bu özelliğe sahip bir şişe taşımam gerekmesi

Çözüm

  • Sevilen şişenin istendiği zaman yeniden tek başına kullanılabilmesi
  • Tak-Çıkar aparatlardan alarak fazladan şişe taşınmaması

Teknik Giriş

Decorator pattern’in işlenişi biraz garip hissettirebiliyor. Çünkü Decorator objelerimiz eklenti olmasına rağmen kodun içinde kullanılırken asıl objeyi sarmalıyor. Bu kısmın sonuna geldiğimizde daha net anlayacaksınız. Şimdilik Şişe arayüzümüzü tanımlayarak başlayabiliriz.

type Sise interface {
Ic() string
}

Çok sade bir arayüz oldu değil mi? Peki neden mi dönüş tipi var? İç dedik de biz ne içtiğimizi de bilmek istiyoruz. Bu yüzden string dönüş tipinde bir fonksiyon olarak tanımladık. Şimdi hikayede çok sevdiğimiz şişemizi temsil edecek yapıyı kuralım.

type SuSisesi struct {
icindekiSu string
}

func (ss *SuSisesi) Ic() string {
return ss.icindekiSu
}

func (ss *SuSisesi) Doldur(sivi string) {
fmt.Println(sivi, "şişeye dolduruluyor...")
ss.icindekiSu = sivi
}

Su şişem içinde barındırdığı suyu kaydedebilmemiz için string bir değişken taşıyor. Göreceğiniz üzere Ic() fonksiyonum da dümdüz bir şekilde bu veriyi dönüyor.

Doldur fonksiyonum ise parametre olarak verdiğim değeri şişenin içindeki veriye eşitliyor. Şimdi asıl işi yapan Aparatlarımıza geçelim. İki adet aparat var. Bunlardan birisi Temizlik Aparatı:

type TemizlemeFiltresi struct {
sise Sise
}

func (tf *TemizlemeFiltresi) Ic() string {
fmt.Println("Temizlik filtresi takıldı")

siseninIcindekiSivi := tf.sise.Ic()
yeniSivi := strings.ReplaceAll(siseninIcindekiSivi, "Kirli ", "")
return yeniSivi
}

Dikkat ettiyseniz ek özellik kazandırmak için oluşturduğum objemin içinde sarmalamak istediğim asıl nesneyi kabul edecek olan Sise arayüzü var. Aparatlarımızı da bu arayüze uygun yaptığımız için aparatlara da aparat takabiliyor hale geleceğiz. Fonksiyonun görevi ise çok basit, yazıdaki “Kirli” kelimesini çıkar. Yani “Kirli Su” diye yolladığım bir yazı bana “Su” olarak geri dönecek. Aroma filtresine gelecek olursak da

type AromaFiltresi struct {
sise Sise
}

func (af *AromaFiltresi) Ic() string {
fmt.Println("Aroma filtresi takıldı")

yeniSivi := "Aromalı " + af.sise.Ic()
return yeniSivi
}

şişeden çıkan suyun başına “Aromalı” kelimesinin eklendiğini açıkça görebilirsiniz. Şimdi gelin bu yapının nasıl kullanıldığını görmek için örnek kodumuza bir göz atalım.

func OrnekCalistir() {
var icindekiSivi string

var suSisesi SuSisesi
suSisesi.Doldur("Kirli Su")

icindekiSivi = suSisesi.Ic()
fmt.Println(icindekiSivi, "içildi")

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

temizlikFiltreli := TemizlemeFiltresi{
sise: &suSisesi,
}
icindekiSivi = temizlikFiltreli.Ic()
fmt.Println(icindekiSivi, "içildi")

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

aromaVeTemizlikFiltreli := AromaFiltresi{
sise: &temizlikFiltreli,
}
icindekiSivi = aromaVeTemizlikFiltreli.Ic()
fmt.Println(icindekiSivi, "içildi")
}

Başlangıçta su şişemi tanımlıyor ve içini Kirli Su ile dolduruyorum. Hiç bir aparat takılmamışken iç fonksiyonunu çalıştırıyorum.

Daha sonrasında Temizlik filtresini takıyorum. Bunu nasıl mı yapıyorum? TemizlikFiltresi yapısından bir obje yaratıp içerisine şişemi veriyorum. Bunu yapmamızın nedeni, tasarımın çıkış noktasına dayanıyor. Asıl objemi değiştirmek istemiyorum ama davranışı değiştirmek istiyorum. Asıl objeye kod yazmadan onu sarmalayarak bunu başarabilirim. Bu yüzden asıl objeyi, eklenti objesinin içindeki bir değişken olarak kullanıyorum. Oradan gelen veriyi de manipüle edip istediğim şekilde bir geri dönüş sağlayabiliyorum.

Aynı işlemi AromaFiltresi için de yapıyorum. Bilmelisiniz ki bunu birden fazla kez yapabiliriz. Gerisi sizin hayal gücünüze kalıyor. Şimdilik bu örnekte kalalım ve çalıştırdığımızda programımızın çıktısı ne oluyormuş, beraber inceleyelim:

Kirli Su şişeye dolduruluyor…
Kirli Su içildi
— — — — — — — — — — — —
Temizlik filtresi takıldı
Su içildi
— — — — — — — — — — — —
Aroma filtresi takıldı
Temizlik filtresi takıldı
Aromalı Su içildi

Sonuç

Artık bu dizaynın ne gibi problemlere çözüm getirdiğini biliyorsunuz. Asıl objenin kodunu değiştirmeden çalışma biçimine ekleme yaptık. Bu da tam olarak SOLID prensiplerinden biri olan Open-Closed prensibine tertemiz uymamızı sağlıyor. Her yerde kullanmaya çalışmak kodunuzu olduğundan daha karmaşık bir hale getirebilir. Bu nedenle gerçekten ihtiyacınız olduğunu hissettiğinizde kullanmanızı tavsiye ederim.

--

--