Go Programming

İdiyomatik Go: Ölçeklenebilir Sistemler İçin Tasarım Desenlerinin Uygulanması

Go (veya Golang), modern bulut-native altyapının omurgası haline hızla geldi. Basitliği, eşzamanlılık modeli ve açık yapısı, verimliliği önemseyen geliştiricileri cezbetmektedir. Ancak Go'ya geçen geliştiriciler arasında yaygın bir yanlış anlama vardır: Java veya C++ gibi dillerdeki tasarım desenlerinden kesinlikle kaçınmaları gerektiği yönünde. Bu yanlıştır. Go tasarım desenlerini benimser ancak daha minimalist ve idiyomatik bir yaklaşımı tercih eder. Bu yazıda, Singleton, Factory ve Observer gibi üç klasik deseni Go ekosistemine doğal hissettirecek şekilde nasıl uygulayacağımızı keşfedeceğiz.

Go Felsefesi: Karmaşıklık Yerine Sadelik

Koda dalmadan önce, Go'nun miras almaya (inheritance) göre bileşenlere (composition) dayalı bir yaklaşımı teşvik ettiğini anlamak önemlidir. Geleneksel GoF (Gang of Four) desenleri genellikle derin miras hiyerarşilerine heavily (ağırlıklı olarak) dayanır. Go'da yapısal sorunları, daha küçük, tek amaçlı arayüzleri bileşenler halinde kullanarak çözeriz. Bu, gevşek bağımlılığı azaltır ve test etmeyi önemli ölçüde kolaylaştırır. Karmaşık soyut sınıflara ihtiyacınız olmasa da, davranışı tanımlamak için arayüzleri ve durumu tutmak için yapıları (structs) sık sık kullanacaksınız.

1. Singleton Deseni: Güvenli ve İdiyomatik

Singleton deseni, bir sınıfın yalnızca bir örneğe sahip olmasını sağlar ve buna erişim için global bir nokta sunar. Tembel başlatma (lazy initialization) destekleyen dillerde, iş parçacığı güvenliği büyük bir endişe kaynağıdır. Go'da, singletonları elemanın idiyomatik yolu, başlatmanın eşzamanlı ortamlarda bile tam olarak bir kez gerçekleşmesini garanti eden sync paketini, özellikle de sync.Once'u kullanmaktır.

package singleton

import "sync"

// DatabaseConn tek bir veritabanı bağlantısını temsil eder.
type DatabaseConn struct {
    Host string
    Port int
}

var (
    instance *DatabaseConn
    once     sync.Once
)

// GetInstance DatabaseConn'un tek örneğini döndürür.
func GetInstance() *DatabaseConn {
    once.Do(func() {
        instance = &DatabaseConn{
            Host: "localhost",
            Port: 5432,
        }
    })
    return instance
}

sync.Once kullanarak, manuel mutex kilitleme ve kilidi açma kontrollerinin gereksiz kodundan (boilerplate) kaçınırız. Bu, global durumun tembel başlatılması için standart kütüphanenin tercih ettiği yöntemdir.

2. Factory Deseni: Oluşturma Mantığının Soyutlanması

Factory desenleri, oluşturulacak nesnenin tam sınıfını belirtmeden nesneler oluşturmak için kullanılır. Go'da bu, genellikle bir arayüz döndüren fonksiyonlar veya yöntemler kullanılarak uygulanır. Bu, tüketicinin kodunu değiştirmeden uygulamaları değiştirmenize olanak tanır; bu da bağımlılık enjeksiyonu ve testler için hayati önem taşır.

package notification

// Notifier mesaj gönderme sözleşmesini tanımlayan arayüzdür.
type Notifier interface {
    Send(message string) error
}

// EmailService Notifier arayüzünü uygular.
type EmailService struct{}

func (e *EmailService) Send(message string) error {
    // E-posta gönderme mantığı
    return nil
}

// SMSService Notifier arayüzünü uygular.
type SMSService struct{}

func (s *SMSService) Send(message string) error {
    // SMS gönderme mantığı
    return nil
}

// NewNotifier, sağlanan türe göre bir Notifier oluşturur.
func NewNotifier(serviceType string) (Notifier, error) {
    switch serviceType {
    case "email":
        return &EmailService{}, nil
    case "sms":
        return &SMSService{}, nil
    default:
        return nil, fmt.Errorf("desteklenmeyen notifier türü: %s", serviceType)
    }
}

Bu yaklaşım, oluşturma mantığını merkezileştirir ve uygulamanın geri kalanının somut yapılar yerine Notifier arayüzüne bağlı kalmasını sağlar.

3. Observer Deseni: Eşzamanlılığı Kolaylaştırma

Go, JavaScript'teki gibi yerleşik olay dinleyicilerine sahip olmasa da, Observer desenini gorutinler ve kanallar (channels) kullanarak uygulayabiliriz. Bu, özellikle sistem olaylarını günlüğe kaydetmek veya bağlı istemcilere güncellemeleri yayınlamak gibi hizmetleri birbirinden bağımsız hale getirmek için kullanışlıdır.

package observer

type EventBus struct {
    subscribers map[string][]chan string
}

func NewEventBus() *EventBus {
    return &EventBus{
        subscribers: make(map[string][]chan string),
    }
}

func (eb *EventBus) Subscribe(event string, ch chan string) {
    eb.subscribers[event] = append(eb.subscribers[event], ch)
}

func (eb *EventBus) Publish(event string, message string) {
    if chs, ok := eb.subscribers[event]; ok {
        for _, ch := range chs {
            go func(c chan string) {
                c <- message
            }(ch)
        }
    }
}

Her bir abone için bir gorutin başlatarak, bir abone yavaşsa yayıncının engellenmemesini sağlarız. Bu, Go'nun eşzamanlılık ilkelevererek, diğer dillerde geleneksel olarak daha karmaşık olan bir deseni uygular.

Sonuç

Go'da tasarım desenleri uygulamak, sıkı şablonları takip etmekten ziyade dilin güçlü yönlerinden (arayüzler, bileşenleme ve eşzamanlılık) yararlanmaktır. Singletonlar için sync.Once, fabrikalar için arayüz döndüren fonksiyonlar ve gözlemciler için kanallar kullanarak, yalnızca işlevsel değil, aynı zamanda idiyomatik ve sürdürülebilir kod yazarsınız. Unutmayın, en iyi Go kodu basit, açıktır ve mümkün olduğunda standart kütüphaneyi kullanır.

Share: