Menu
Coddy logo textTech

Wzorzec opcji funkcyjnych

Część sekcji Programowanie obiektowe ścieżki GO w Coddy. Lekcja 79 z 107.

Podczas tworzenia struktur z wieloma opcjonalnymi polami konfiguracji funkcje konstruktora mogą stać się nieporęczne. Wzorzec opcji funkcyjnych elegancko rozwiązuje ten problem, wykorzystując funkcje do konfigurowania obiektów.

Rozważ strukturę serwera z wieloma opcjonalnymi ustawieniami. Zamiast konstruktora z wieloma parametrami definiujemy typ opcji i funkcje, które zwracają opcje:

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
    }
}

Konstruktor przyjmuje listę opcji o zmiennej długości i stosuje każdą z nich:

func NewServer(host string, opts ...Option) *Server {
    s := &Server{
        host:    host,
        port:    8080,    // domyślna wartość
        timeout: 30,      // domyślna wartość
    }
    for _, opt := range opts {
        opt(s)
    }
    return s
}

Teraz tworzenie serwerów staje się czytelne i elastyczne:

// Użyj wartości domyślnych
s1 := NewServer("localhost")

// Dostosuj wybrane opcje
s2 := NewServer("localhost", WithPort(3000), WithTimeout(60))

Ten wzorzec sprawdza się doskonale, ponieważ wywołujący określają tylko to, czego potrzebują, wartości domyślne są jasno zdefiniowane w jednym miejscu, a dodawanie nowych opcji nie powoduje błędów w istniejącym kodzie. Jest powszechnie stosowany w bibliotekach Go, takich jak grpc i zap.

challenge icon

Wyzwanie

Łatwy

Zbudujmy konfigurowalne połączenie z bazą danych przy użyciu wzorca opcji funkcyjnych! Ten wzorzec sprawdza się doskonale, gdy masz wiele opcjonalnych ustawień z rozsądnymi wartościami domyślnymi — dokładnie tego potrzebuje połączenie z bazą danych.

Rozmieścisz kod w dwóch plikach:

  • database.go: Zdefiniuj typ połączenia z bazą danych oraz funkcje opcji.

    Utwórz strukturę DBConnection z następującymi nieeksportowanymi polami:

    • host (string)
    • port (int)
    • username (string)
    • password (string)
    • maxConnections (int)
    • timeout (int) - w sekundach

    Zdefiniuj typ Option jako funkcję modyfikującą *DBConnection.

    Utwórz następujące funkcje opcji, które zwracają wartość typu Option:

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

    Utwórz konstruktor NewDBConnection(host string, opts ...Option), który zwraca *DBConnection. Ustaw następujące wartości domyślne: port 5432, username "admin", password "secret", maxConnections 10, timeout 30. Zastosuj wszystkie podane opcje po ustawieniu wartości domyślnych.

    Dodaj metodę ConnectionString(), która zwraca informacje o połączeniu w następującym formacie:

    [username]:[password]@[host]:[port] (max:[maxConnections], timeout:[timeout]s)
  • main.go: Twórz połączenia z bazą danych z różnymi konfiguracjami.

    Wczytaj hosta, a następnie wczytaj liczbę opcji do zastosowania. Dla każdej opcji wczytaj jej typ i wartość lub wartości:

    • port, a następnie numer portu
    • credentials, a następnie nazwę użytkownika i hasło (dwa wiersze)
    • maxconn, a następnie maksymalną liczbę połączeń
    • timeout, a następnie limit czasu w sekundach

    Utwórz DBConnection z określonymi opcjami i wyświetl jego ciąg połączenia.

Podane zostaną następujące dane wejściowe:

  • Wiersz 1: Nazwa hosta
  • Wiersz 2: Liczba opcji
  • Kolejne wiersze: Typ opcji i wartość lub wartości

Na przykład dla danych:

localhost
0

Oczekiwany wynik:

admin:secret@localhost:5432 (max:10, timeout:30s)

A dla danych:

db.example.com
2
port
3306
credentials
root
mypassword

Oczekiwany wynik:

root:mypassword@db.example.com:3306 (max:10, timeout:30s)

A dla danych:

production.server
4
port
5433
credentials
dbuser
securepass123
maxconn
50
timeout
60

Oczekiwany wynik:

dbuser:securepass123@production.server:5433 (max:50, timeout:60s)

Zwróć uwagę, że wywołujący podają tylko potrzebne opcje: nieokreślone ustawienia przyjmują rozsądne wartości domyślne. Na tym polega elegancja wzorca opcji funkcyjnych!

Spróbuj swoich sił

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// Odczytaj host
	host, _ := reader.ReadString('\n')
	host = strings.TrimSpace(host)

	// Odczytaj liczbę opcji
	numOptionsStr, _ := reader.ReadString('\n')
	numOptions, _ := strconv.Atoi(strings.TrimSpace(numOptionsStr))

	// Zbierz opcje
	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: Dodaj opcję WithPort do opts
			_ = port // Usuń tę linię podczas implementacji

		case "credentials":
			username, _ := reader.ReadString('\n')
			username = strings.TrimSpace(username)
			password, _ := reader.ReadString('\n')
			password = strings.TrimSpace(password)
			// TODO: Dodaj opcję WithCredentials do opts
			_, _ = username, password // Usuń tę linię podczas implementacji

		case "maxconn":
			maxStr, _ := reader.ReadString('\n')
			maxConn, _ := strconv.Atoi(strings.TrimSpace(maxStr))
			// TODO: Dodaj opcję WithMaxConnections do opts
			_ = maxConn // Usuń tę linię podczas implementacji

		case "timeout":
			timeoutStr, _ := reader.ReadString('\n')
			timeout, _ := strconv.Atoi(strings.TrimSpace(timeoutStr))
			// TODO: Dodaj opcję WithTimeout do opts
			_ = timeout // Usuń tę linię podczas implementacji
		}
	}

	// TODO: Utwórz DBConnection za pomocą NewDBConnection, używając host i opts
	// TODO: Wypisz ciąg połączenia za pomocą metody ConnectionString()

	_ = opts // Usuń tę linię podczas implementacji
	fmt.Println("TODO: Print connection string here")
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator Go online