Dependency Injection
Coddy'nin GO Journey'sinin Nesne Yönelimli Programlama bölümünün bir parçası — ders 45 / 107.
Dependency Injection, bir struct'ın bağımlılıklarını dahili olarak oluşturmak yerine dışarıdan aldığı bir tekniktir. Go'da, interfaces (arayüzler) bu deseni doğal ve güçlü kılar.
Bir struct içine belirli bir uygulamayı doğrudan yazmak (hardcoding) yerine, bir arayüz (interface) kabul edersiniz. Bu, struct'ın kodunu değiştirmeden uygulamaları değiştirmenize olanak tanır:
type Notifier interface {
Send(message string) string
}
type EmailNotifier struct{}
func (e EmailNotifier) Send(message string) string {
return "Email: " + message
}
type SMSNotifier struct{}
func (s SMSNotifier) Send(message string) string {
return "SMS: " + message
}
Şimdi somut bir türe değil, arayüze bağlı olan bir struct oluşturun:
type OrderService struct {
notifier Notifier // bağımlılık arayüz üzerinden enjekte edildi
}
func NewOrderService(n Notifier) *OrderService {
return &OrderService{notifier: n}
}
func (o *OrderService) PlaceOrder(item string) string {
return o.notifier.Send("Order placed: " + item)
}
OrderService, e-posta mı yoksa SMS mi kullandığını bilmez veya umursamaz. Servisi oluştururken bağımlılığı enjekte edersiniz:
func main() {
emailService := NewOrderService(EmailNotifier{})
fmt.Println(emailService.PlaceOrder("Book")) // Email: Sipariş verildi: Book
smsService := NewOrderService(SMSNotifier{})
fmt.Println(smsService.PlaceOrder("Phone")) // SMS: Sipariş verildi: Phone
}
Bu yaklaşım, kodunuzu daha esnek ve test edilebilir hale getirir. Test sırasında, aslında mesaj göndermeyen sahte (mock) bir bildirimci (notifier) enjekte edebilirsiniz. Üretim ortamında gerçek uygulamayı enjekte edersiniz. Her iki senaryoda da struct değişmeden kalır.
Görev
KolayBağımlılık enjeksiyonunun (dependency injection) gücünü gösteren bir günlükleme (logging) sistemi oluşturalım. Günlükleri farklı hedeflere yazabilen bir servis oluşturacaksınız; üstelik bu servis, günlüklerin gerçekte nereye gittiğini bilmek veya umursamak zorunda kalmayacak.
Kodunuzu üç dosya halinde düzenleyeceksiniz:
logger.go: BirLog(message string) stringmetodu gerektiren birLoggerarayüzü (interface) tanımlayın. Ardından iki farklı günlükçü (logger) uygulaması oluşturun:Prefixalanına sahipConsoleLogger—Logmetodu[CONSOLE] [Prefix]: [message]değerini döndürür.Filenamealanına sahipFileLogger—Logmetodu[FILE:[Filename]] [message]değerini döndürür.
service.go:Loggerarayüzüne (somut bir türe değil) bağımlı olan birAppServiceyapısı (struct) oluşturun. BirLoggerkabul eden veAppService'e bir işaretçi (pointer) döndürenNewAppServiceadlı bir yapıcı (constructor) fonksiyon ekleyin.AppService'e, enjekte edilen günlükçüyü kullanarakProcessing: [task]mesajını günlükleyen ve günlükçüden dönen sonucu döndürenDoWork(task string) stringadlı bir metot verin.main.go: Yapılandırmayı girdiden okuyun, her iki günlükçü türünü de oluşturun, her birini ayrıAppServiceörneklerine enjekte edin ve sağlanan görevle her servisteDoWorkmetodunu çağırın. Her servis çağrısından gelen sonucu yazdırın.
Aşağıdaki girdiler sağlanacaktır:
- Satır 1: Konsol günlükçü öneki (prefix)
- Satır 2: Dosya günlükçü dosya adı (filename)
- Satır 3: İşlenecek görev (task)
Örneğin, INFO, app.log ve user authentication verildiğinde, çıktınız şu şekilde olmalıdır:
[CONSOLE] INFO: Processing: user authentication
[FILE:app.log] Processing: user authenticationAppService'in bir konsola mı yoksa bir dosyaya mı günlükleme yaptığını nasıl bilmediğine dikkat edin; sadece enjekte edilen günlükçü neyse onun üzerindeki Log metodunu çağırır. Bu esneklik, bağımlılık enjeksiyonunun özüdür: aynı servis kodu, tamamen farklı günlükleme uygulamalarıyla çalışır.
Kopya kağıdı
Dependency Injection (Bağımlılık Enjeksiyonu), bir struct'ın bağımlılıklarını dahili olarak oluşturmak yerine dışarıdan aldığı bir tekniktir. Go'da arayüzler (interfaces) bu deseni doğal ve güçlü kılar.
Bağımlılık için bir arayüz tanımlayın:
type Notifier interface {
Send(message string) string
}
Arayüzün somut uygulamalarını oluşturun:
type EmailNotifier struct{}
func (e EmailNotifier) Send(message string) string {
return "Email: " + message
}
type SMSNotifier struct{}
func (s SMSNotifier) Send(message string) string {
return "SMS: " + message
}
Somut bir tipe değil, arayüze bağlı olan bir struct oluşturun:
type OrderService struct {
notifier Notifier // bağımlılık arayüz aracılığıyla enjekte edildi
}
func NewOrderService(n Notifier) *OrderService {
return &OrderService{notifier: n}
}
func (o *OrderService) PlaceOrder(item string) string {
return o.notifier.Send("Order placed: " + item)
}
Servisi oluştururken bağımlılığı enjekte edin:
emailService := NewOrderService(EmailNotifier{})
fmt.Println(emailService.PlaceOrder("Book")) // Email: Order placed: Book
smsService := NewOrderService(SMSNotifier{})
fmt.Println(smsService.PlaceOrder("Phone")) // SMS: Order placed: Phone
Bu yaklaşım, struct'ın kodunu değiştirmeden farklı uygulamaların değiştirilmesine olanak tanıyarak kodu daha esnek ve test edilebilir hale getirir.
Kendin dene
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Konsol günlükçü önekini oku
prefix, _ := reader.ReadString('\n')
prefix = prefix[:len(prefix)-1]
// Dosya günlükçü dosya adını oku
filename, _ := reader.ReadString('\n')
filename = filename[:len(filename)-1]
// İşlenecek görevi oku
task, _ := reader.ReadString('\n')
if len(task) > 0 && task[len(task)-1] == '\n' {
task = task[:len(task)-1]
}
// TODO: Önek ile bir ConsoleLogger oluşturun
// TODO: Dosya adı ile bir FileLogger oluşturun
// TODO: ConsoleLogger enjekte edilmiş bir AppService oluşturun
// TODO: FileLogger enjekte edilmiş başka bir AppService oluşturun
// TODO: Her bir serviste görev ile DoWork metodunu çağırın ve sonuçları yazdırın
fmt.Println("result1")
fmt.Println("result2")
}
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