Menu
Coddy logo textTech

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.

challenge icon

Görev

Kolay

Fonksiyonel 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 DBConnection yapısı (struct) oluşturun:

    • host (string)
    • port (int)
    • username (string)
    • password (string)
    • maxConnections (int)
    • timeout (int) - saniye cinsinden

    Bir *DBConnection nesnesini değiştiren bir fonksiyon olarak Option türünü tanımlayın.

    Bir Option döndüren şu seçenek fonksiyonlarını oluşturun:

    • WithPort(port int)
    • WithCredentials(username, password string)
    • WithMaxConnections(max int)
    • WithTimeout(seconds int)

    Bir *DBConnection döndüren NewDBConnection(host string, opts ...Option) yapılandırıcısını (constructor) oluşturun. Şu varsayılanları ayarlayın: port 5432, username "admin", password "secret", maxConnections 10, timeout 30. 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:

    • port ve ardından port numarası
    • credentials ve ardından kullanıcı adı ve şifre (iki satır)
    • maxconn ve ardından maksimum bağlantı sayısı
    • timeout ve ardından saniye cinsinden zaman aşımı süresi

    Belirtilen seçeneklerle bir DBConnection oluş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")
}
quiz iconKendini test et

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