Design Patterns & Golang: Strategy 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

Siz bir komutansınız ve elinizde çok yetenekli bir askeriniz var. Bir operasyona göndereceksiniz ve telsiz yoluyla iletişim halindesiniz. Aranızda iletişim problemi olduğu için sizi çok yoracak bir diyalog yaşanıyor:

Siz: Giyin ve hazırlan. 
Asker: Bu Gizli mi yoksa Normal bir görev mi?
Siz: Gizli bir görev
Asker: O zaman kamufle olabileceğim şekilde hazırlanıyorum
Siz: Tehçizatın hazır mı?
Asker: Bu Gizli mi yoksa Normal bir görev mi?
Siz: Gizli bir görev
Asker: O zaman silahıma susturucu takıyorum
Siz: Görev yerine doğru harekete geç
Asker: Bu Gizli mi yoksa Normal bir görev mi?
Siz: Hasta mısın canım sen?

Oysa askerinizle aranızdaki bu iletişim sorununu çözerseniz daha efektif bir operasyon süreci olacaktır. Bundan sonra verdiğiniz her emri başta tanımladığınız tipe göre gerçekleştirecektir.

Siz: Asker Hazırlan, Gizli bir göreve gidiyorsun.
Asker: Anlaşıldı, emirlerinize uyarken buna dikkat edeceğim
# Asker olay yerine varmış ve bir rakip görmüştür #
Siz: Düşmandan kurtul
Asker: Hedefi sessizce etkisiz hale getiriyorum

Problem

  • Emirlerin uygulanış şeklinin her seferinde yeniden sorgulanması
  • Her yeni seçenek geldiğinde(Casusluk görevi, Rehine görevi vb.) Askerin sorusunun daha uzun olacağı ve emri yerine getirmesinin gecikecek olması

Çözüm

  • Emirlerin uygulanış şeklinin en başta tanımlanan tipe göre belirlenmesi

Teknik Giriş

Hikayemizdeki ilk diyaloğu teknik olarak özetleyerek başlamak istiyorum. Orada Asker nesnesinin içinde her hareketimiz için if bloğuna girdiğimizi ifade etmek istedim. Dört adet seçeneğimiz olsaydı her fonksiyonun içinde 4 adet if olacaktı. Bu sağlıklı bir yaklaşım değil. Hem okunabilirliği hem de karmaşıklığı arttırıyor. Hadi şimdi bu hikayeyi modellerken bu sorunu nasıl aştığımıza bir göz atalım.

Öncelikle Asker için bir interface tanımlayarak bunu uygulayacak yapılardan neler beklediğimizi gösterelim.

type Asker interface {
Giyin()
TehcizatHazirla()
HareketEt()
EtkisizHaleGetir()
}

Bu adımdan sonra artık stratejilerimizi belirleyebiliriz. Askerimizin Normal görevlerde farklı, Gizli görevlerde farklı davranmasını istiyoruz. Bu nedenle ikisi için de Asker sınıfının isteklerine uyan objeler oluşturacağız. Normal görevler için şu şekilde bir tasarım yapalım.

type Normal struct{}

func (n Normal) Giyin() {
fmt.Println("Sağlam bir çelik yelek ve havalı asker kostümü giyiliyor")
}
func (n Normal) TehcizatHazirla() {
fmt.Println("M4A1 ve şarjörleri hazırlanıyor")
}
func (n Normal) HareketEt() {
fmt.Println("Hedef konuma doğru harekete geçiliyor")
}
func (n Normal) EtkisizHaleGetir() {
fmt.Println("Düşman askeri ortadan kaldırıldı")
}

Her zamanki gibi fonksiyonların içine temsilen yazılar bıraktık. Yazılardaki olayların oraya kodlandığını hayal edebilirsiniz. Şimdi de benzer bir tasarımı Gizli görevler için yapacağız

type Gizli struct{}

func (g Gizli) Giyin() {
fmt.Println("Hafif bir çelik yelek ve ortamda kamufle olunabilecek şekilde giyiliyor")
}
func (g Gizli) TehcizatHazirla() {
fmt.Println("USP tactical ve şarjör hazırlanıyor. Susturucu takılıyor. ")
}
func (g Gizli) HareketEt() {
fmt.Println("Ses çıkarmadan yavaşça harekete geçiliyor")
}
func (g Gizli) EtkisizHaleGetir() {
fmt.Println("Düşman askeri sessizce etkisiz hale getirildi")
}

Ana kodumuza geçmeden önce operasyon tipini askerimize bildirebilmek için de bir fonksiyon yazmalıyız.

func OperasyonTarziBelirle(gorevTarzi string) Asker {
switch gorevTarzi {
case "Normal":
return Normal{}
case "Gizli":
return Gizli{}
default:
panic("Asker bu operayon için uygun değil")
}
}

Burada görüldüğü üzere askere verilen talimata göre gerekli obje geriye döndürülüyor. Son olarak hikayemizi gerçekleştirmek için çalıştıracağımız ana kod bloğunu hazırlayabiliriz.

func OrnekCalistir() {
var Atilla Asker

fmt.Println("------------ Normal Görev --------------")

Atilla = OperasyonTarziBelirle("Normal")
Atilla.Giyin()
Atilla.TehcizatHazirla()
Atilla.HareketEt()
Atilla.EtkisizHaleGetir()

fmt.Println("------------ Gizli Görev --------------")

Atilla = OperasyonTarziBelirle("Gizli")
Atilla.Giyin()
Atilla.TehcizatHazirla()
Atilla.HareketEt()
Atilla.EtkisizHaleGetir()
}

Örnek üzerinden gidecek olursak, ben parametre olarak “Gizli” verisini gönderdiğimde benim asker objem olan Atilla gizli objesinin fonksiyonlarını çalıştıracak şekilde hareket edecektir. Eğer “Normal” verisini gönderirsem de normal objesinin fonksiyonlarında belirtildiği gibi davranacaktır. Bu örneğe göre çıktımız da şu şekilde olacaktır:

— — — — — — Normal Görev — — — — — — —
Sağlam bir çelik yelek ve havalı asker kostümü giyiliyor
M4A1 ve şarjörleri hazırlanıyor
Hedef konuma doğru harekete geçiliyor
Düşman askeri ortadan kaldırıldı
— — — — — — Gizli Görev — — — — — — —
Hafif bir çelik yelek ve ortamda kamufle olunabilecek şekilde giyiliyor
USP tactical ve şarjör hazırlanıyor. Susturucu takılıyor.
Ses çıkarmadan yavaşça harekete geçiliyor
Düşman askeri sessizce etkisiz hale getirildi

Sonuç

Artık bu dizaynın ne gibi problemlere çözüm getirdiğini biliyorsunuz. Tasarımın güzel tarafı ne kadar strateji objesi oluşturursanız oluşturun, fonksiyonların içini if bloklarıyla doldurmak zorunda değilsiniz. Yazdığınız tek if hangi stratejiyi döneceğinize karar verdiğiniz fonksiyon olacak, verdiğiniz stratejiye göre de objenizin davranışlarını değiştireceksiniz. Asıl odaklanmanız gereken kodları tespit ve analiz edebilmek eskisine göre çok daha kolay olacaktır.

--

--