Functional Options Deseni
Coddy'nin GO Journey'sinin Nesne Yönelimli Programlama bölümünün bir parçası — ders 79 / 107.
Birçok isteğe bağlı yapılandırma alanına sahip struct'lar oluştururken, yapıcı fonksiyonlar hantal hale gelebilir. Fonksiyonel Seçenekler Deseni (Functional Options Pattern), nesneleri yapılandırmak için fonksiyonlar kullanarak bu sorunu zarif bir şekilde çözer.
Birden fazla isteğe bağlı ayarı olan bir sunucu yapısını (struct) düşünün. Çok sayıda parametreye sahip bir yapıcı (constructor) yerine, bir seçenek türü ve seçenekler döndüren fonksiyonlar tanımlarız:
type Server struct {
host string
port int
timeout int
}
type Option func(*Server)
func WithPort(port int) Option {
return func(s *Server) {
s.port = port
}
}
func WithTimeout(timeout int) Option {
return func(s *Server) {
s.timeout = timeout
}
}Constructor, değişken sayıda seçenek listesi kabul eder ve her birini uygular:
func NewServer(host string, opts ...Option) *Server {
s := &Server{
host: host,
port: 8080, // varsayılan
timeout: 30, // varsayılan
}
for _, opt := range opts {
opt(s)
}
return s
}Artık sunucu oluşturmak okunabilir ve esnek hale geliyor:
// Varsayılanları kullan
s1 := NewServer("localhost")
// Belirli seçenekleri özelleştir
s2 := NewServer("localhost", WithPort(3000), WithTimeout(60))Bu desen, çağıranların yalnızca ihtiyaç duyduklarını belirtmesi, varsayılanların tek bir yerde net olması ve yeni seçenekler eklemenin mevcut kodu bozmaması nedeniyle öne çıkar. grpc ve zap gibi Go kütüphanelerinde yaygın olarak kullanılır.
Görev
KolayFonksiyonel Seçenekler Desenini (Functional Options Pattern) kullanarak yapılandırılabilir bir veritabanı bağlantısı oluşturalım! Bu desen, mantıklı varsayılan değerlere sahip birçok isteğe bağlı ayarınız olduğunda parlar—tam da bir veritabanı bağlantısının ihtiyaç duyduğu şey.
Kodunuzu iki dosya halinde düzenleyeceksiniz:
database.go: Veritabanı bağlantı türünüzü ve seçenek fonksiyonlarınızı tanımlayın.Şu dışa aktarılmamış (unexported) alanlara sahip bir
DBConnectionyapısı (struct) oluşturun:host(string)port(int)username(string)password(string)maxConnections(int)timeout(int) - saniye cinsinden
Bir
*DBConnectionnesnesini değiştiren bir fonksiyon olarakOptiontürünü tanımlayın.Bir
Optiondöndüren şu seçenek fonksiyonlarını oluşturun:WithPort(port int)WithCredentials(username, password string)WithMaxConnections(max int)WithTimeout(seconds int)
Bir
*DBConnectiondöndürenNewDBConnection(host string, opts ...Option)yapılandırıcısını (constructor) oluşturun. Şu varsayılanları ayarlayın: port5432, username"admin", password"secret", maxConnections10, timeout30. Varsayılanları ayarladıktan sonra sağlanan tüm seçenekleri uygulayın.Bağlantı bilgisini şu formatta döndüren bir
ConnectionString()metodu ekleyin:[username]:[password]@[host]:[port] (max:[maxConnections], timeout:[timeout]s)main.go: Çeşitli yapılandırmalarla veritabanı bağlantıları oluşturun.Host bilgisini okuyun, ardından uygulanacak seçeneklerin sayısını okuyun. Her seçenek için seçenek türünü ve değerini/değerlerini okuyun:
portve ardından port numarasıcredentialsve ardından kullanıcı adı ve şifre (iki satır)maxconnve ardından maksimum bağlantı sayısıtimeoutve ardından saniye cinsinden zaman aşımı süresi
Belirtilen seçeneklerle bir
DBConnectionoluşturun ve bağlantı dizesini (connection string) yazdırın.
Aşağıdaki girdiler sağlanacaktır:
- Satır 1: Host adı
- Satır 2: Seçenek sayısı
- Sonraki satırlar: Seçenek türü ve değer(ler)i
Örneğin, şu girdi verildiğinde:
localhost
0Çıktınız şu olmalıdır:
admin:secret@localhost:5432 (max:10, timeout:30s)Ve şu girdi verildiğinde:
db.example.com
2
port
3306
credentials
root
mypasswordÇıktınız şu olmalıdır:
root:mypassword@db.example.com:3306 (max:10, timeout:30s)Ve şu girdi verildiğinde:
production.server
4
port
5433
credentials
dbuser
securepass123
maxconn
50
timeout
60Çıktınız şu olmalıdır:
dbuser:securepass123@production.server:5433 (max:50, timeout:60s)Çağıranların yalnızca ihtiyaç duydukları seçenekleri nasıl belirttiklerine dikkat edin—belirtilmeyen ayarlar mantıklı varsayılanları kullanır. Fonksiyonel Seçenekler Deseninin zarafeti buradadır!
Kopya kağıdı
Functional Options Deseni, uzun yapıcı (constructor) parametreleri yerine fonksiyonlar kullanarak birçok isteğe bağlı alana sahip struct'ları yapılandırmak için zarif bir yol sunar.
Struct'ınıza bir işaretçiyi (pointer) değiştiren bir fonksiyon olarak bir Option türü tanımlayın:
type Server struct {
host string
port int
timeout int
}
type Option func(*Server)Option closure'larını döndüren seçenek fonksiyonları oluşturun:
func WithPort(port int) Option {
return func(s *Server) {
s.port = port
}
}
func WithTimeout(timeout int) Option {
return func(s *Server) {
s.timeout = timeout
}
}Yapıcı (constructor), değişken sayıda (variadic) seçenek listesi kabul eder, varsayılanları ayarlar ve ardından her bir seçeneği uygular:
func NewServer(host string, opts ...Option) *Server {
s := &Server{
host: host,
port: 8080, // varsayılan
timeout: 30, // varsayılan
}
for _, opt := range opts {
opt(s)
}
return s
}Kullanım temiz ve esnektir; çağıranlar yalnızca ihtiyaç duyduklarını belirtir:
// Varsayılanları kullan
s1 := NewServer("localhost")
// Belirli seçenekleri özelleştir
s2 := NewServer("localhost", WithPort(3000), WithTimeout(60))Avantajları: varsayılanlar merkezileştirilmiştir, çağıranlar yalnızca ihtiyaç duyduklarını belirtir ve yeni seçenekler eklemek mevcut kodu bozmaz.
Kendin dene
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Host'u oku
host, _ := reader.ReadString('\n')
host = strings.TrimSpace(host)
// Seçenek sayısını oku
numOptionsStr, _ := reader.ReadString('\n')
numOptions, _ := strconv.Atoi(strings.TrimSpace(numOptionsStr))
// Seçenekleri topla
var opts []Option
for i := 0; i < numOptions; i++ {
optionType, _ := reader.ReadString('\n')
optionType = strings.TrimSpace(optionType)
switch optionType {
case "port":
portStr, _ := reader.ReadString('\n')
port, _ := strconv.Atoi(strings.TrimSpace(portStr))
// TODO: WithPort seçeneğini opts'a ekle
_ = port // Uygularken bu satırı kaldırın
case "credentials":
username, _ := reader.ReadString('\n')
username = strings.TrimSpace(username)
password, _ := reader.ReadString('\n')
password = strings.TrimSpace(password)
// TODO: WithCredentials seçeneğini opts'a ekle
_, _ = username, password // Uygularken bu satırı kaldırın
case "maxconn":
maxStr, _ := reader.ReadString('\n')
maxConn, _ := strconv.Atoi(strings.TrimSpace(maxStr))
// TODO: WithMaxConnections seçeneğini opts'a ekle
_ = maxConn // Uygularken bu satırı kaldırın
case "timeout":
timeoutStr, _ := reader.ReadString('\n')
timeout, _ := strconv.Atoi(strings.TrimSpace(timeoutStr))
// TODO: WithTimeout seçeneğini opts'a ekle
_ = timeout // Uygularken bu satırı kaldırın
}
}
// TODO: host ve opts ile NewDBConnection kullanarak DBConnection oluştur
// TODO: ConnectionString() metodunu kullanarak bağlantı dizesini yazdır
_ = opts // Uygularken bu satırı kaldırın
fmt.Println("TODO: Print connection string here")
}
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 Pool12İleri Düzey OOP Kavramları
Functional Options DeseniGo'da Builder DeseniMetot ZincirlemeTür Takma Adları vs. TanımlamalarReflection TemelleriKod Üretimine Genel Bakış