Design Patterns & Golang: Chain of Responsibility 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

Pizzaları çok seviyorsunuz. Hem de o kadar çok seviyorsunuz ki gidip bir pizza üretim fabrikasında kalite kontrol uzmanı olarak çalışmaya karar verdiniz. Fabrikanın kapısından içeri girdiniz ve ağzınız açık kaldı. Diyorsunuz ki böyle pizza mı üretilir? Kocaman bir makinaya yukarıdan hamuru bırakıyorlar, kim bilir içeride neler oluyor. Hangi aşamalardan geçtiğini anlamak için de uzun uzun bi inceleme gerekiyor.

Makinadan çıkan pizzaların tadına bir bakmak istediniz. Baharatlamanın eksik olduğunu farkettiniz. Gerekli kişilere dediniz ki bu üretim akışına baharatlama da ekleyelim. Mühendis de size “Kolaysa sen ekle kardeşim” diyerek cevap verdi. Aslında bir bakıma haklı…

Kolları sıvadınız ve müdürü ikna edip bu makinayı sattırdınız. Onun yerine gerekli aşamaları tek tek uygulatabileceğiniz birden fazla makina aldınız. İşlemleri bittikten sonra çıkan ürünü kendisinden bir sonraki makinaya aktaracak şekilde bir sistem kurdunuz. Artık sisteminize yeni bir aşama eklemek çok kolaylaştı. Son makinanın ucunu istediğiniz başka bir makinaya bağlayabilirsiniz. Araya da rahatça farklı bir işlem sokabilirsiniz. Bu hikayede kazanan fabrika, kaybeden siz oldunuz çünkü bir daha asla eskisi gibi pizza sevemeyeceksiniz.

Problem

  • Tüm işlemlerin kocaman bir makinada yapılması
  • Makinanın incelenmesinin zor olması
  • Yeni bir işlem eklenmek istendiğinde karmaşıklığın artması

Çözüm

  • Kendine ait görevi olan birden fazla makinanın kullanılması
  • Makinaların doğru sırayla bir sonraki makinaya bağlanılması

Teknik Giriş

İştah açabilecek bu örnekte hızlıca modellemeye geçip açlığımızı unutabiliriz. Hadi Pizza objesiyle başlayıp bir an önce aradan çıkaralım.

type Pizza struct {
hamur string
sos string
malzemeler []PizzaMalzemeleri
}

type PizzaMalzemeleri string

const (
Kasar PizzaMalzemeleri = "Kasar"
Sucuk = "Sucuk"
Sosis = "Sosis"
Zeytin = "Zeytin"
Misir = "Misir"
Biber = "Biber"
)

Pizzayı yeterince temsil edebilecek, içerisinde malzemeleri ve yapısına dair bilgileri tutan bir obje tasarımı yapıyoruz. Sonrasında da işinde gücünde olacak makinalarımızın uygulamasını istediğimiz arayüzü tasarlayacağız.

type Makina interface {
GoreviniYap(pizza Pizza)
SonrakiMakinayiBelirle(makina Makina)
}

Makinalarımızdan görevini yapmasını ve sonraki makinaya bağlanabilecek kapasitede olmasını istiyoruz. Bu noktadan itibaren artık makinalarımızı tanımlayabiliriz.

type HamurMakinasi struct {
sonrakiMakina Makina
}

func (hm *HamurMakinasi) GoreviniYap(pizza Pizza) {
fmt.Println("Hamur kalınlık tercihine göre açılıyor:", pizza.hamur)
//----------İşlemler----------
fmt.Println("Hamur başarıyla açıldı")
if hm.sonrakiMakina != nil {
hm.sonrakiMakina.GoreviniYap(pizza)
}
}

func (hm *HamurMakinasi) SonrakiMakinayiBelirle(makina Makina) {
hm.sonrakiMakina = makina
}
type SosMakinasi struct {
sonrakiMakina Makina
}

func (sm *SosMakinasi) GoreviniYap(pizza Pizza) {
fmt.Println("Sos istege göre dökülüyor:", pizza.sos)
//----------İşlemler----------
fmt.Println("Sos başarıyla döküldü")
if sm.sonrakiMakina != nil {
sm.sonrakiMakina.GoreviniYap(pizza)
}
}

func (sm *SosMakinasi) SonrakiMakinayiBelirle(makina Makina) {
sm.sonrakiMakina = makina
}
type MalzemeMakinasi struct {
sonrakiMakina Makina
}

func (mm *MalzemeMakinasi) GoreviniYap(pizza Pizza) {
fmt.Println("İstenen malzemeler pizzaya ekleniyor:", pizza.malzemeler)
//----------İşlemler----------
fmt.Println("İstenen malzemeler pizzaya eklendi")
if mm.sonrakiMakina != nil {
mm.sonrakiMakina.GoreviniYap(pizza)
}
}

func (mm *MalzemeMakinasi) SonrakiMakinayiBelirle(makina Makina) {
mm.sonrakiMakina = makina
}
type PisirmeMakinasi struct {
sonrakiMakina Makina
}

func (pm *PisirmeMakinasi) GoreviniYap(pizza Pizza) {
fmt.Println("Pişirme işlemi başladı")
//----------İşlemler----------
fmt.Println("Pişirme işlemi tamamlandı")
if pm.sonrakiMakina != nil {
pm.sonrakiMakina.GoreviniYap(pizza)
}
}

func (pm *PisirmeMakinasi) SonrakiMakinayiBelirle(makina Makina) {
pm.sonrakiMakina = makina
}

Makinalarımızın içleri birbirine çok benziyor. Hepsi kendi işine dair log basıyor ve bağlı olduğu bir makina varsa pizzayı ona iletiyor. Her makina tek bir işi yapacak şekilde planlandığı için de analizi daha kolay oluyor. Artık sanal fabrikamızı oluşturacağımız ana kod bloğuna geçebiliriz.

func OrnekCalistir() {
var (
hamurMakinasi HamurMakinasi
sosMakinasi SosMakinasi
malzemeMakinasi MalzemeMakinasi
pisirmeMakinasi PisirmeMakinasi

sucukluKasarliPizza, karisikPizza Pizza
)

hamurMakinasi.SonrakiMakinayiBelirle(&sosMakinasi)
sosMakinasi.SonrakiMakinayiBelirle(&malzemeMakinasi)
malzemeMakinasi.SonrakiMakinayiBelirle(&pisirmeMakinasi)

sucukluKasarliPizza = Pizza{
hamur: "İnce",
sos: "Normal",
malzemeler: []PizzaMalzemeleri{Kasar, Sucuk},
}
hamurMakinasi.GoreviniYap(sucukluKasarliPizza)

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

karisikPizza = Pizza{
hamur: "Normal",
sos: "Bol",
malzemeler: []PizzaMalzemeleri{Kasar, Sucuk, Sosis, Zeytin, Misir, Biber},
}
hamurMakinasi.GoreviniYap(karisikPizza)

}

Önce makinalarımızı tanımladık. Daha sonrasında doğru sırayla uçlarını bir sonraki makinaya bağladık. Artık sistemimiz hazır ve istediğimiz pizzayı üretime sokabiliriz. Örneğimizde bir adet Sucuklu Kaşarlı Pizza, bir adet de Karışık Pizza üretiyoruz.

Hadi gelin o zaman programın çıktısına bir göz atalım:

Hamur kalınlık tercihine göre açılıyor: İnce
Hamur başarıyla açıldı
Sos istege göre dökülüyor: Normal
Sos başarıyla döküldü
İstenen malzemeler pizzaya ekleniyor: [Kasar Sucuk]
İstenen malzemeler pizzaya eklendi
Pişirme işlemi başladı
Pişirme işlemi tamamlandı
— — — — — — — — — — — —
Hamur kalınlık tercihine göre açılıyor: Normal
Hamur başarıyla açıldı
Sos istege göre dökülüyor: Bol
Sos başarıyla döküldü
İstenen malzemeler pizzaya ekleniyor: [Kasar Sucuk Sosis Zeytin Misir Biber]
İstenen malzemeler pizzaya eklendi
Pişirme işlemi başladı
Pişirme işlemi tamamlandı

Sonuç

Artık bu dizaynın ne gibi problemlere çözüm getirdiğini biliyorsunuz. Bütün sorumlulukları tek bir yerde tanımlamaktansa bu şekilde modüler bir yaklaşım kullanmak kodu esnek ve temiz bir hale getirebiliyor. Ancak hazırladığınız arayüze tüm objelerinizin uyması gerektiğini ve her birine aynı parametreyi geçmek zorunda olduğunuzu dikkate almayı unutmayın.

--

--