Observer Kalıbı
Coddy'nin GO Journey'sinin Nesne Yönelimli Programlama bölümünün bir parçası — ders 89 / 107.
Observer deseni, nesneler arasında bire-çok bir ilişki tanımlar. Bir nesne (subject) durum değiştirdiğinde, tüm bağımlıları (observers) otomatik olarak bilgilendirilir. Bu; olay sistemleri, bildirimler veya birden fazla bileşenin değişikliklere tepki vermesi gereken her türlü senaryo için idealdir.
Go'da, bu deseni gözlemciler için bir arayüz ve abonelikleri yöneten bir konu yapısı (struct) kullanarak uygularız:
type Observer interface {
Update(event string)
}
type Subject struct {
observers []Observer
}
func (s *Subject) Subscribe(o Observer) {
s.observers = append(s.observers, o)
}
func (s *Subject) Notify(event string) {
for _, observer := range s.observers {
observer.Update(event)
}
}Observer arayüzünü uygulayan herhangi bir tür, bildirim almak için abone olabilir:
type EmailAlert struct{ Address string }
func (e EmailAlert) Update(event string) {
fmt.Printf("Email to %s: %s\n", e.Address, event)
}
type Logger struct{}
func (l Logger) Update(event string) {
fmt.Println("Log:", event)
}Subject, somut türlerini bilmeden tüm gözlemcileri bilgilendirir:
subject := &Subject{}
subject.Subscribe(EmailAlert{Address: "user@example.com"})
subject.Subscribe(Logger{})
subject.Notify("Order placed")
// user@example.com adresine e-posta: Order placed
// Log: Order placedBu desen, özneyi gözlemcilerinden ayırarak mevcut kodu değiştirmeden yeni gözlemci türleri eklemeyi kolaylaştırır. Olay güdümlü mimarilerde, GUI çerçevelerinde ve yayınla/abone ol (pub/sub) mesajlaşma sistemlerinde yaygın olarak kullanılır.
Görev
KolayObserver desenini kullanarak bir hisse senedi fiyat alarm sistemi kuralım! Birden fazla alarm servisinin, bir hisse senedi fiyatı her değiştiğinde otomatik olarak bildirim aldığı bir sistem oluşturacaksınız; bu, gözlemcilerin (observers), konunun (subject) onların somut türlerini bilmesine gerek kalmadan olaylara nasıl tepki verebileceğini göstermek için mükemmeldir.
Kodunuzu üç dosya halinde düzenleyeceksiniz:
observer.go: Gözlemci arayüzünüzü ve somut gözlemci türlerinizi tanımlayın.Fiyatlar değiştiğinde hisse senedi bilgilerini alan
Update(stockName string, price float64)metoduna sahip birObserverarayüzü oluşturun.İki gözlemci türü uygulayın:
Thresholdalanına (float64) sahipPriceAlert— güncellendiğinde, fiyat eşiğin üzerindeyseALERT: [stockName] at $[price] crossed threshold $[threshold], altındaysaWATCHING: [stockName] at $[price] (threshold: $[threshold])döndürür.LogNamealanına (string) sahipPriceLogger— güncellendiğinde[logName] recorded [stockName]: $[price]döndürür.
Her iki gözlemci de biçimlendirilmiş yanıt dizelerini döndüren bir metoda sahip olmalıdır.
stock.go: Gözlemcileri yöneten ve fiyat değişikliklerini onlara bildiren konunuzu (subject) oluşturun.Name(string),Price(float64) ve bir gözlemci dilimi (slice) için alanları olan birStockyapısı oluşturun. Şu metodları ekleyin:- Bir gözlemci eklemek için
Subscribe(o Observer) - Fiyatı güncelleyen, tüm gözlemcileri bilgilendiren ve tüm yanıtlarının bir dilimini döndüren
SetPrice(price float64) []string
Ayrıca bir
NewStock(name string, initialPrice float64) *Stockyapıcı fonksiyonu (constructor) oluşturun.- Bir gözlemci eklemek için
main.go: Hisse senedinizi gözlemcilerle kurun ve fiyat değişikliklerini simüle edin.Hisse senedi adını ve başlangıç fiyatını okuyun. Ardından oluşturulacak gözlemci sayısını okuyun. Her gözlemci için türünü (
alertveyalogger) ve yapılandırmasını (alarmlar için eşik, loglayıcılar için log adı) okuyun. Her gözlemciyi hisse senedine abone yapın.Son olarak, yeni bir fiyat okuyun, hisse senedini güncelleyin ve her gözlemcinin yanıtını kendi satırına yazdırın.
Aşağıdaki girdiler sağlanacaktır:
- Satır 1: Hisse senedi adı
- Satır 2: Başlangıç fiyatı
- Satır 3: Gözlemci sayısı
- Her gözlemci için: tür (
alertveyalogger), ardından yapılandırma değeri - Son satır: Ayarlanacak yeni fiyat
Tüm fiyatları iki ondalık basamakla biçimlendirin.
Örneğin, şu girdi verildiğinde:
GOOG
150.00
3
alert
155.00
logger
TradeLog
alert
140.00
160.50Çıktınız şöyle olmalıdır:
ALERT: GOOG at $160.50 crossed threshold $155.00
TradeLog recorded GOOG: $160.50
ALERT: GOOG at $160.50 crossed threshold $140.00Ve şu girdi verildiğinde:
AAPL
180.00
2
logger
DailyLog
alert
200.00
175.25Çıktınız şöyle olmalıdır:
DailyLog recorded AAPL: $175.25
WATCHING: AAPL at $175.25 (threshold: $200.00)Hisse senedinin (Stock), alarmları mı yoksa loglayıcıları mı bilgilendirdiğini bilmediğine dikkat edin; sadece her gözlemci üzerinde Update metodunu çağırır. Bu ayrıştırma (decoupling), Stock kodunu değiştirmeden yeni gözlemci türleri eklemeyi kolaylaştırır!
Kopya kağıdı
Observer deseni, bir öznenin (subject) durumu değiştiğinde birden fazla gözlemciyi (observer) otomatik olarak bilgilendirdiği bire-çok bir ilişki tanımlar. Bu, özneyi gözlemcilerinden ayırarak (decoupling) olay sistemleri ve bildirimler için ideal hale getirir.
Temel Yapı
Bir gözlemci arayüzü ve abonelikleri yöneten bir özne tanımlayın:
type Observer interface {
Update(event string)
}
type Subject struct {
observers []Observer
}
func (s *Subject) Subscribe(o Observer) {
s.observers = append(s.observers, o)
}
func (s *Subject) Notify(event string) {
for _, observer := range s.observers {
observer.Update(event)
}
}Gözlemcileri Uygulama
Observer arayüzünü uygulayan herhangi bir tür abone olabilir:
type EmailAlert struct{ Address string }
func (e EmailAlert) Update(event string) {
fmt.Printf("Email to %s: %s\n", e.Address, event)
}
type Logger struct{}
func (l Logger) Update(event string) {
fmt.Println("Log:", event)
}Kullanım
Gözlemcileri abone yapın ve değişiklikleri onlara bildirin:
subject := &Subject{}
subject.Subscribe(EmailAlert{Address: "user@example.com"})
subject.Subscribe(Logger{})
subject.Notify("Order placed")
// Email to user@example.com: Order placed
// Log: Order placedÖzne, somut türlerini bilmeden tüm gözlemcileri bilgilendirir; bu da mevcut kodu değiştirmeden yeni gözlemci türleri eklemeyi kolaylaştırır.
Kendin dene
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Hisse senedi adını oku
stockName, _ := reader.ReadString('\n')
stockName = strings.TrimSpace(stockName)
// Başlangıç fiyatını oku
initialPriceStr, _ := reader.ReadString('\n')
initialPrice, _ := strconv.ParseFloat(strings.TrimSpace(initialPriceStr), 64)
// Gözlemci sayısını oku
numObserversStr, _ := reader.ReadString('\n')
numObservers, _ := strconv.Atoi(strings.TrimSpace(numObserversStr))
// TODO: NewStock kurucusunu kullanarak yeni bir Stock oluştur
// TODO: Her bir gözlemciyi döngüye al
for i := 0; i < numObservers; i++ {
// Gözlemci türünü oku ("alert" veya "logger")
observerType, _ := reader.ReadString('\n')
observerType = strings.TrimSpace(observerType)
// Yapılandırma değerini oku
configValue, _ := reader.ReadString('\n')
configValue = strings.TrimSpace(configValue)
// TODO: Türe göre uygun gözlemciyi oluştur
// - "alert" için: configValue değerini float64 eşik değeri olarak ayrıştır, PriceAlert oluştur
// - "logger" için: configValue değerini LogName olarak kullan, PriceLogger oluştur
// TODO: Gözlemciyi hisse senedine abone yap
}
// Yeni fiyatı oku
newPriceStr, _ := reader.ReadString('\n')
newPrice, _ := strconv.ParseFloat(strings.TrimSpace(newPriceStr), 64)
// TODO: Yeni fiyatı ayarla ve yanıtları al
// TODO: Her bir yanıtı kendi satırına yazdır
}
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