Singleton Kalıbı
Coddy'nin GO Journey'sinin Nesne Yönelimli Programlama bölümünün bir parçası — ders 86 / 107.
Singleton deseni, bir türün programınız boyunca yalnızca bir örneğe sahip olmasını sağlar ve ona küresel bir erişim noktası sunar. Bu, yapılandırma yöneticileri, veritabanı bağlantıları veya günlükçüler gibi paylaşılan kaynaklar için kullanışlıdır.
Go'da, iş parçacığı açısından güvenli (thread-safe) başlatmayı garanti etmek için sync.Once ile birleştirilmiş paket düzeyinde bir değişken kullanarak Singleton'ı uygularız:
package config
import "sync"
type Config struct {
DatabaseURL string
MaxRetries int
}
var (
instance *Config
once sync.Once
)
func GetInstance() *Config {
once.Do(func() {
instance = &Config{
DatabaseURL: "localhost:5432",
MaxRetries: 3,
}
})
return instance
}sync.Once, birden fazla goroutine GetInstance() fonksiyonunu eşzamanlı olarak çağırsa bile, başlatma fonksiyonunun tam olarak bir kez çalışmasını garanti eder. Sonraki her çağrı, başlatma kodunu yeniden yürütmeden aynı örneği döndürür.
cfg1 := config.GetInstance()
cfg2 := config.GetInstance()
// cfg1 ve cfg2 aynı örneği işaret ederSingleton'ı idareli kullanın. Kullanışlı olsa da, testi zorlaştırabilen ve bağımlılıkları gizleyebilen global bir durum (global state) sunar. Kullanım durumunuz için bağımlılık enjeksiyonunun (dependency injection) daha iyi bir alternatif olup olmayacağını değerlendirin.
Görev
KolayUygulamanız boyunca yalnızca bir logger örneğinin var olmasını sağlayan bir Logger singleton oluşturalım! Bu, Singleton deseninin klasik bir kullanım örneğidir; programınızın tüm bölümlerinin aynı logger yapılandırmasını ve durumunu paylaşmasını istersiniz.
Kodunuzu iki dosya halinde düzenleyeceksiniz:
logger.go: İş parçacığı güvenli (thread-safe) Singleton logger'ınızı uygulayın.İki alana sahip bir
Loggeryapısı (struct) oluşturun: günlük mesajı önekleri içinPrefix(string) ve kaç mesajın günlüğe kaydedildiğini takip etmek içinMessageCount(int).Yalnızca bir örneğin oluşturulduğundan emin olmak için
sync.Onceile paket düzeyinde değişkenler kullanın. Logger'ı varsayılan"[LOG]"önekiyle başlatan ve singleton örneğini döndüren birGetLogger()fonksiyonu uygulayın.Logger'ınıza şu metotları ekleyin:
SetPrefix(prefix string)- logger'ın önekini değiştirirLog(message string) string- mesaj sayısını artırır ve biçimlendirilmiş bir dize döndürür:[prefix] #[count]: [message]GetCount() int- günlüğe kaydedilen toplam mesaj sayısını döndürür
main.go:GetLogger()fonksiyonuna yapılan birden fazla çağrının aynı örneği döndürdüğünü gösterin.Yeni bir önek değeri okuyun, ardından bir mesaj sayısı ve ardından günlüğe kaydedilecek her bir mesajı okuyun.
Logger örneğini alın, özel öneki ayarlayın, ardından her mesajı günlüğe kaydedin ve sonucu yazdırın. Tüm mesajları günlüğe kaydettikten sonra, logger örneğini tekrar alın (aynı örnek olduğunu kanıtlamak için) ve toplam mesaj sayısını yazdırın.
Aşağıdaki girdiler sağlanacaktır:
- Satır 1: Ayarlanacak özel önek
- Satır 2: Mesaj sayısı
- Sonraki satırlar: Günlüğe kaydedilecek her bir mesaj
Örneğin, şu girdi verildiğinde:
[APP]
3
Server started
User connected
Request processedÇıktınız şu olmalıdır:
[APP] #1: Server started
[APP] #2: User connected
[APP] #3: Request processed
Total messages logged: 3Ve şu girdi verildiğinde:
[DEBUG]
2
Initializing cache
Cache readyÇıktınız şu olmalıdır:
[DEBUG] #1: Initializing cache
[DEBUG] #2: Cache ready
Total messages logged: 2Ve şu girdi verildiğinde:
[ERROR]
1
Connection failedÇıktınız şu olmalıdır:
[ERROR] #1: Connection failed
Total messages logged: 1Buradaki temel nokta, GetLogger() fonksiyonunu kaç kez çağırırsanız çağırın, her zaman paylaşılan durumuyla birlikte aynı örneği almanızdır. Mesaj sayısı kalıcıdır çünkü var olan tek bir Logger vardır!
Kopya kağıdı
Singleton deseni, bir türün programınız boyunca yalnızca bir örneğe sahip olmasını sağlar ve bu örneğe küresel bir erişim noktası sunar. Bu, yapılandırma yöneticileri, veritabanı bağlantıları veya günlükçüler (loggers) gibi paylaşılan kaynaklar için kullanışlıdır.
İş parçacığı güvenli (thread-safe) bir başlatma için sync.Once ile birleştirilmiş paket düzeyinde bir değişken kullanarak Singleton'ı uygulayın:
package config
import "sync"
type Config struct {
DatabaseURL string
MaxRetries int
}
var (
instance *Config
once sync.Once
)
func GetInstance() *Config {
once.Do(func() {
instance = &Config{
DatabaseURL: "localhost:5432",
MaxRetries: 3,
}
})
return instance
}sync.Once, birden fazla goroutine aynı anda GetInstance() fonksiyonunu çağırsa bile, başlatma fonksiyonunun tam olarak bir kez çalışmasını garanti eder. Sonraki her çağrı aynı örneği döndürür:
cfg1 := config.GetInstance()
cfg2 := config.GetInstance()
// cfg1 and cfg2 point to the same instanceSingleton'ı idareli kullanın; çünkü test etmeyi zorlaştırabilen ve bağımlılıkları gizleyebilen küresel bir durum (global state) sunar. Alternatif olarak bağımlılık enjeksiyonunu (dependency injection) değerlendirin.
Kendin dene
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Özel öneki oku
prefix, _ := reader.ReadString('\n')
prefix = strings.TrimSpace(prefix)
// Mesaj sayısını oku
countStr, _ := reader.ReadString('\n')
countStr = strings.TrimSpace(countStr)
count, _ := strconv.Atoi(countStr)
// Her bir mesajı oku
messages := make([]string, count)
for i := 0; i < count; i++ {
msg, _ := reader.ReadString('\n')
messages[i] = strings.TrimSpace(msg)
}
// TODO: GetLogger() kullanarak logger örneğini al
// TODO: SetPrefix() kullanarak özel öneki ayarla
// TODO: Her mesajı günlüğe kaydet ve sonucu yazdır
// TODO: Logger örneğini tekrar al (aynısı olduğunu kanıtlamak için)
// ve toplam mesaj sayısını şu formatta yazdır: "Total messages logged: X"
}
Bu ders kısa bir quiz içerir. Soruları yanıtlamak ve ilerlemeni kaydetmek için derse başla.
Nesne Yönelimli Programlama bölümündeki tüm dersler
1Go OOP Temelleri
Harici DosyalarGo Çalışma Alanı ve ModüllerPaketler ve İçe AktarmalarDışa Aktarılan ve Aktarılmayan İsimlerGo'da OOP'ye GirişSınıf Olarak Struct'larStruct'larda Metot TanımlamaPointer ve Değer AlıcılarStruct BaşlatmaYapıcı FonksiyonlarÖzet - Basit Hesap Makinesi4Arayüzler
Arayüzlere GirişÖrtük UygulamaSözleşme Olarak ArayüzBoş Arayüz (any)Tür OnaylamaTür SeçimiArayüz BileşimiStringer ve Error ArayüzleriÖzet - Şekil Hesaplayıcı7Kapsülleme
Dışa Aktarılan ve Aktarılmayan AlanlarPaket Seviyesinde KapsüllemeGetter ve Setter MetotlarıGo'da Bilgi GizlemeÖzet - Öğrenci Kayıtları10Generics (Go 1.18+)
Generics'e GirişTür ParametreleriTür KısıtlamalarıGeneric Struct'larGeneric Metotlar İçin Geçici ÇözümÖzet - Generic Koleksiyon13Tasarım Kalıpları Bölüm 1
Tasarım Kalıplarına GirişSingleton KalıbıFactory KalıbıAbstract Factory KalıbıObserver KalıbıStrategy Kalıbı2Türler ve Struct'lar: Derinlemesine İnceleme
Temel ve Bileşik TürlerÖzel Tür TanımlamalarıStruct EtiketleriAnonim Struct'larİç İçe Geçmiş Struct'larSıfır Değerler ve VarsayılanlarÖzet - Rehber5Kalıtım Yerine Kompozisyon
Go'da Neden Kalıtım YokStruct Gömme TemelleriMetot YükseltmeBirden Fazla Struct GömmeGömme vs AgregasyonGömülü Metotları GölgelemeÖzet - Çalışan Hiyerarşisi8Hata Yönetimi ve OOP
error ArayüzüÖzel Hata TürleriHata Sarmalama (fmt.Errorf)Sentinel Hatalarerrors.Is() ve errors.As()Panic, Defer ve RecoverÖzet - Dosya Ayrıştırıcı3İşaretçiler ve Bellek
Go'da İşaretçi TemelleriStruct İşaretçileriDeğer ile Geçme ve Referans ile Geçmenew() FonksiyonuGo'da Garbage CollectionÖzet - Linked List Oluşturucu6Go'da Polimorfizm
Interface'ler ile PolimorfizmGo'da Duck TypingInterface Karşılama KurallarıPolimorfik KoleksiyonlarDependency InjectionÖzet - Payment Processor9Eşzamanlılık ve OOP
Goroutine TemelleriKanallar ve İletişimTamponlu ve Tamponsuz KanallarSelect İfadesisync.Mutex ve sync.RWMutexsync.WaitGroupThread-Safe Struct TasarımıÖzet - Worker Pool