Sözleşme Olarak Arayüz
Coddy'nin GO Journey'sinin Nesne Yönelimli Programlama bölümünün bir parçası — ders 27 / 107.
Arayüzleri (interfaces) sözleşmeler olarak düşünmek, daha iyi Go programları tasarlamanıza yardımcı olur. Bir fonksiyon bir arayüz türünü kabul ettiğinde şunu demiş olur: "Bu sözleşmeyi yerine getirdiği sürece, bana hangi somut türü (concrete type) verdiğin umurumda değil."
Bu sözleşme tabanlı düşünme biçimi, belirli türlerden ziyade davranışa odaklanan fonksiyonlar yazmanıza olanak tanır. Çeşitli kaynaklardan gelen verileri işlemesi gereken bir fonksiyonu ele alalım:
type DataSource interface {
FetchData() string
}
func ProcessData(src DataSource) {
data := src.FetchData()
fmt.Println("Processing:", data)
}ProcessData fonksiyonu, bir veritabanı bağlantısı, bir API istemcisi veya bir dosya okuyucu alıp almadığını bilmez veya umursamaz. Sadece girdinin FetchData() yapabilmesini gerektirir. Bu sözleşmeye uyan her tür çalışır.
type Database struct{ ConnectionString string }
func (d Database) FetchData() string { return "data from DB" }
type APIClient struct{ Endpoint string }
func (a APIClient) FetchData() string { return "data from API" }
func main() {
db := Database{ConnectionString: "localhost"}
api := APIClient{Endpoint: "https://api.example.com"}
ProcessData(db) // Çalışıyor!
ProcessData(api) // Bu da çalışıyor!
}Bu sözleşme yaklaşımı kodunuzu daha test edilebilir hale de getirir. Test sırasında, gerçek veritabanlarına veya ağ bağlantılarına ihtiyaç duymadan arayüzü (interface) karşılayan bir taklit (mock) tür oluşturabilirsiniz. Test edilen fonksiyon aradaki farkı anlamaz çünkü sözleşme yerine getirilmiştir.
Görev
KolayArayüzlerin (interfaces) nasıl sözleşme olarak hareket ettiğini gösteren bir bildirim sistemi kuralım. Bir bildirimin gönderilmesinin ne anlama geldiğini tanımlayan bir Notifier arayüzü oluşturacak ve ardından bu sözleşmeye uyan birden fazla bildirim kanalı uygulayacaksınız.
Kodunuzu iki dosya halinde düzenleyeceksiniz:
notifiers.go: Tek birNotify(message string) stringmetoduna sahip birNotifierarayüzü tanımlayın. Ardından bu sözleşmeyi karşılayan üç farklı struct oluşturun:Prefixalanına sahipConsoleNotifierFilenamealanına sahipFileNotifierURLalanına sahipWebhookNotifier
Notifymetodunu uygulamalıdır.main.go: BirNotifierdilimi (slice) ve bir mesaj dizesi kabul edenBroadcastAlertadında bir fonksiyon oluşturun. Bu fonksiyon, her bir bildirimci üzerindeNotifymetodunu çağırmalı ve sonuçları yazdırmalıdır. Yapılandırmayı girdiden okuyun, her üç bildirimci türünü de oluşturun ve hepsinden bir uyarı yayınlayın.
Aşağıdaki girdiler sağlanacaktır:
- Satır 1: Konsol öneki (Console prefix)
- Satır 2: Dosya adı (Filename)
- Satır 3: Webhook URL'si
- Satır 4: Uyarı mesajı (Alert message)
Notify metodlarınız şu formatlarda dizeler döndürmelidir:
- ConsoleNotifier:
[Prefix] [message] - FileNotifier:
Writing to [Filename]: [message] - WebhookNotifier:
POST [URL]: [message]
Örneğin, ALERT:, log.txt, https://hooks.example.com ve Server down! verildiğinde, çıktınız şu şekilde olmalıdır:
ALERT: Server down!
Writing to log.txt: Server down!
POST https://hooks.example.com: Server down!Bu tasarımın gücü, BroadcastAlert fonksiyonunuzun belirli bildirimci türlerini bilmemesi veya umursamamasıdır; yalnızca her birinin Notifier sözleşmesini yerine getirmesini gerektirir. Daha sonra yayın fonksiyonunu hiç değiştirmeden bir SlackNotifier veya EmailNotifier ekleyebilirsiniz.
Kopya kağıdı
Go'daki arayüzler (interfaces), somut türleri belirtmeden gerekli davranışı tanımlayan sözleşmeler (contracts) olarak işlev görür. Bir fonksiyon bir arayüz türünü kabul ettiğinde, arayüzün metotlarını uygulayan herhangi bir tür kullanılabilir.
Metot imzalarını belirterek bir arayüz tanımlayın:
type DataSource interface {
FetchData() string
}Arayüzleri kabul eden fonksiyonlar, sözleşmeyi karşılayan her türle çalışır:
func ProcessData(src DataSource) {
data := src.FetchData()
fmt.Println("Processing:", data)
}Birden fazla tür aynı arayüzü uygulayabilir:
type Database struct{ ConnectionString string }
func (d Database) FetchData() string { return "data from DB" }
type APIClient struct{ Endpoint string }
func (a APIClient) FetchData() string { return "data from API" }
func main() {
db := Database{ConnectionString: "localhost"}
api := APIClient{Endpoint: "https://api.example.com"}
ProcessData(db) // Çalışır!
ProcessData(api) // Bu da çalışır!
}Bu sözleşme tabanlı yaklaşım şunları sağlar:
- Belirli türlerden ziyade davranışa odaklanan fonksiyonlar
- Mock uygulamaları aracılığıyla daha iyi test edilebilirlik
- Mevcut kodu değiştirmeden genişletilebilirlik
Kendin dene
package main
import (
"bufio"
"fmt"
"os"
)
// BroadcastAlert tüm bildirimciler aracılığıyla bir mesaj gönderir
// TODO: Bu fonksiyonu şunları yapacak şekilde uygulayın:
// - Bir Notifier değerleri dilimi (slice) ve bir mesaj dizesi kabul etsin
// - Her bir bildirimci üzerinde Notify metodunu çağırsın ve sonuçları yazdırsın
func BroadcastAlert(notifiers []Notifier, message string) {
// TODO: Bildirimciler arasında döngü kurun ve her bir bildirim sonucunu yazdırın
}
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Konsol önekini oku
scanner.Scan()
prefix := scanner.Text()
// Dosya adını oku
scanner.Scan()
filename := scanner.Text()
// Webhook URL'sini oku
scanner.Scan()
url := scanner.Text()
// Uyarı mesajını oku
scanner.Scan()
message := scanner.Text()
// TODO: Üç bildirimci türünün de örneklerini oluşturun
// - Önek ile ConsoleNotifier
// - Dosya adı ile FileNotifier
// - URL ile WebhookNotifier
// TODO: Üç bildirimciyi de içeren bir Notifier dilimi oluşturun
// TODO: Bildirimciler ve mesaj ile BroadcastAlert fonksiyonunu çağırın
_ = prefix
_ = filename
_ = url
_ = message
fmt.Println("TODO: Implement the solution")
}
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