Go (Golang), bulut-native altyapı ve yüksek performanslı arka uç sistemleri için hızla tercih edilen dil haline geldi. Felsefesi sadeliği, açık eşzamanlılığı ve minimalizmi vurgular. Java, C++ veya C# gibi dillerden geçiş yapan geliştiriciler için ağır nesne yönelimli tasarım desenlerini uygulama isteği doğaldır. Ancak Go'nun miras almaya değil bileşenlere ve sınıflara değil arayüzlere öncelik veren benzersiz kimliği, mimari tasarım için nüanslı bir yaklaşım gerektirir. Bu yazı, yazılımlarınızın hem sağlam hem de sürdürülebilir kalmasını sağlayarak klasik tasarım desenlerini Go'nun yerel kod stillerine nasıl uyarlayacağınızı keşfeder.
Go Tarzı: Miras Alma Yerine Bileşen Kullanımı
Go'yu benimserken düşünce yapısında yaşanan en önemli değişim, "bir-a" ilişkisine (miras alma) dayanan geleneksel yaklaşımlardan vazgeçip "bir-şey-var" ilişkisine (bileşen kullanımı) geçmektir. Geleneksel tasarım desenleri genellikle derin miras hiyerarşilerine dayanır ki Go bunu açıkça tavsiye etmez. Bunun yerine, Go geliştiricileri davranışı gömmek için anonim alanları ve sözleşmeleri tanımlamak için arayüz gömmeyi kullanır.
Örneğin, bir Database yapısı ve bir RedisClient yapınız varsa, bir üst sınıf StorageEngine oluşturmak yerine bir arayüz tanımlarsınız. Bu, kodunuzu belirli uygulamalardan soyutlar ve çalışma zamanında bileşenleri dinamik olarak değiştirmenize olanak tanır; bu özellik, Go'yu test ve modüler tasarım için son derece esnek kılar.
Strateji Deseninin Uygulanması
Strateji deseni, bir aile algoritma tanımlar, her birini kapsüler ve bunları birbirinin yerine kullanılabilir hale getirir. Go'da bu, arayüzler aracılığıyla doğal olarak gerçekleştirilir. Bu desen, çalışma zamanı koşullarına göre davranışları değiştirmeniz gerektiğinde, örneğin farklı günlük formatları veya ödeme ağ geçitleri seçerken özellikle kullanışlıdır.
Bir ödeme işleme sistemini ele alalım. Bir Payer arayüzü tanımlayabilir ve Kredi Kartı ve PayPal için somut stratejiler uygulayabiliriz.
package main
import "fmt"
// Payer arayüzü strateji sözleşmesini tanımlar
type Payer interface {
Pay(amount float64) error
}
// CreditCard yapısı somut bir stratejiyi temsil eder
type CreditCard struct{}
func (c CreditCard) Pay(amount float64) error {
fmt.Printf("Kredi Kartı üzerinden $%.2f yükleniyor\n", amount)
return nil
}
// PayPal yapısı başka bir somut stratejiyi temsil eder
type PayPal struct{}
func (p PayPal) Pay(amount float64) error {
fmt.Printf("PayPal üzerinden $%.2f yükleniyor\n", amount)
return nil
}
// ProcessPayment kullanımı gösterir
func ProcessPayment(p Payer, amount float64) {
p.Pay(amount)
}
func main() {
processor := CreditCard{}
ProcessPayment(processor, 99.99)
payment := PayPal{}
ProcessPayment(payment, 49.50)
}
Bu yaklaşım, ProcessPayment işlevini altta yatan ödeme yönteminden habersiz tutarak Açık/Kapalı İlkesine uyar. Mevcut kodu değiştirmeden yeni ödeme yöntemleri ekleyebilirsiniz.
Go'da Singleton Deseni
Çok sayıda nesne yönelimli dilde, Singleton'lar özel yapıcılar ve statik yöntemler aracılığıyla uygulanır. Statik yöntemler ve yapıcılar bulunmayan Go, küresel durumu farklı şekilde yönetir. Go'da Singleton benzeri davranış için standart uygulama sync.Once paketini kullanmaktır. Bu, yüksek eşzamanlı ortamlarda bile başlatma mantığının tam olarak bir kez çalışmasını sağlar ve açık kilitlenme maliyeti olmadan iş parçacığı güvenliğini sağlar.
package main
import (
"fmt"
"sync"
)
type Config struct {
Host string
}
var (
instance *Config
once sync.Once
)
func GetConfig() *Config {
once.Do(func() {
instance = &Config{Host: "localhost"}
})
return instance
}
Bu desen, çoğaltmanın verimsiz veya mantıksal olarak yanlış olduğu veritabanı bağlantıları, günlükleyici örnekleri veya uygulama genelinde yapılandırmalar gibi durumları yönetmek için hayati önem taşır.
Nesne Oluşturma İçin Fabrika Deseni
Fabrika desenleri, anlık oluşturma mantığını soyutlayarak, istemcinin oluşturma mantığından habersiz olmasını sağlayarak nesneler oluşturmanıza olanak tanır. Go'da fabrika işlevleri, arayüzleri veya yapıları döndüren basit işlevlerdir. Bu, yerel bir yaklaşımdır ve sınıf tabanlı fabrikalara göre genellikle tercih edilir.
Pratik bir örnek bir Shape (Şekil) fabrikasıdır. Kullanıcı girdisine bağlı olarak ya bir Circle (Daire) ya da bir Rectangle (Dikdörtgen) döndürürsünüz. Bir arayüz döndürerek istemci kodu temiz kalır ve belirli türlerden bağımsız olur.
type Shape interface {
Area() float64
}
func NewShape(shapeType string) Shape {
switch shapeType {
case "circle":
return &Circle{radius: 5}
case "rectangle":
return &Rectangle{width: 10, height: 20}
default:
return nil
}
}
Sonuç
Go'da tasarım desenlerini uygulamak, GoF (Gang of Four) tanımlarına katı bir bağlılıktan çok, Go'nun temel ilkelerini benimsemekle ilgilidir: sadelik, arayüzler ve bileşen kullanımı. Miras alma yerine arayüzleri tercih ederek ve sync.Once gibi eşzamanlılık ilkelereini kullanarak ölçeklenebilir, test edilebilir ve temiz sistemler oluşturabilirsiniz. Unutmayın, her sorun için bir tasarım deseni gerekmez. Bazen basit bir işlev veya yapı en iyi çözümdür. Bu desenleri, her kod parçası için bir gereklilik olarak değil, karmaşıklığı çözmek için bir araç olarak kullanın.