Menu
Coddy logo textTech

Functional-Options-Pattern

Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy. Lektion 79 von 107.

Beim Erstellen von Strukturen mit vielen optionalen Konfigurationsfeldern können Konstruktorfunktionen unhandlich werden. Das Functional-Options-Muster löst dies elegant, indem Funktionen zum Konfigurieren von Objekten verwendet werden.

Betrachte eine Serverstruktur mit mehreren optionalen Einstellungen. Anstatt eines Konstruktors mit vielen Parametern definieren wir einen Option-Typ und Funktionen, die Optionen zurückgeben:

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

Der Konstruktor akzeptiert eine variadische Liste von Optionen und wendet jede einzelne an:

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

Jetzt wird das Erstellen von Servern lesbar und flexibel:

// Standardwerte verwenden
s1 := NewServer("localhost")

// Bestimmte Optionen anpassen
s2 := NewServer("localhost", WithPort(3000), WithTimeout(60))

Dieses Muster überzeugt, weil Aufrufer nur angeben, was sie benötigen, die Standardwerte an einer Stelle klar ersichtlich sind und das Hinzufügen neuer Optionen bestehenden Code nicht beeinträchtigt. Es wird häufig in Go-Bibliotheken wie grpc und zap verwendet.

challenge icon

Aufgabe

Einfach

Lass uns eine konfigurierbare Datenbankverbindung mit dem Functional Options Pattern erstellen! Dieses Muster ist besonders nützlich, wenn du viele optionale Einstellungen mit sinnvollen Standardwerten hast – genau das, was eine Datenbankverbindung benötigt.

Du organisierst deinen Code über zwei Dateien:

  • database.go: Definiere deinen Datenbankverbindungstyp und die Optionsfunktionen.

    Erstelle eine DBConnection-Struktur mit diesen nicht exportierten Feldern:

    • host (string)
    • port (int)
    • username (string)
    • password (string)
    • maxConnections (int)
    • timeout (int) – in Sekunden

    Definiere einen Option-Typ als Funktion, die eine *DBConnection verändert.

    Erstelle diese Optionsfunktionen, die eine Option zurückgeben:

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

    Erstelle einen Konstruktor NewDBConnection(host string, opts ...Option), der eine *DBConnection zurückgibt. Lege diese Standardwerte fest: port 5432, username "admin", password "secret", maxConnections 10, timeout 30. Wende alle bereitgestellten Optionen an, nachdem die Standardwerte festgelegt wurden.

    Füge eine ConnectionString()-Methode hinzu, die die Verbindungsinformationen in diesem Format zurückgibt:

    [username]:[password]@[host]:[port] (max:[maxConnections], timeout:[timeout]s)
  • main.go: Erstelle Datenbankverbindungen mit verschiedenen Konfigurationen.

    Lies den Host ein und anschließend die Anzahl der anzuwendenden Optionen. Lies für jede Option den Optionstyp und deren Wert(e) ein:

    • port, gefolgt von der Portnummer
    • credentials, gefolgt von Benutzername und Passwort (zwei Zeilen)
    • maxconn, gefolgt von der Anzahl der maximalen Verbindungen
    • timeout, gefolgt vom Timeout in Sekunden

    Erstelle eine DBConnection mit den angegebenen Optionen und gib ihren Verbindungsstring aus.

Die folgenden Eingaben werden bereitgestellt:

  • Zeile 1: Hostname
  • Zeile 2: Anzahl der Optionen
  • Folgende Zeilen: Optionstyp und Wert(e)

Zum Beispiel bei folgender Eingabe:

localhost
0

Deine Ausgabe sollte folgendermaßen aussehen:

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

Und bei folgender Eingabe:

db.example.com
2
port
3306
credentials
root
mypassword

Deine Ausgabe sollte folgendermaßen aussehen:

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

Und bei folgender Eingabe:

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

Deine Ausgabe sollte folgendermaßen aussehen:

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

Beachte, dass Aufrufer nur die Optionen angeben, die sie benötigen: Nicht angegebene Einstellungen verwenden sinnvolle Standardwerte. Das ist die Eleganz des Functional Options Pattern!

Probier es selbst

package main

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

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

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

	// Anzahl der Optionen lesen
	numOptionsStr, _ := reader.ReadString('\n')
	numOptions, _ := strconv.Atoi(strings.TrimSpace(numOptionsStr))

	// Optionen sammeln
	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-Option zu opts hinzufügen
			_ = port // Diese Zeile bei der Implementierung entfernen

		case "credentials":
			username, _ := reader.ReadString('\n')
			username = strings.TrimSpace(username)
			password, _ := reader.ReadString('\n')
			password = strings.TrimSpace(password)
			// TODO: WithCredentials-Option zu opts hinzufügen
			_, _ = username, password // Diese Zeile bei der Implementierung entfernen

		case "maxconn":
			maxStr, _ := reader.ReadString('\n')
			maxConn, _ := strconv.Atoi(strings.TrimSpace(maxStr))
			// TODO: WithMaxConnections-Option zu opts hinzufügen
			_ = maxConn // Diese Zeile bei der Implementierung entfernen

		case "timeout":
			timeoutStr, _ := reader.ReadString('\n')
			timeout, _ := strconv.Atoi(strings.TrimSpace(timeoutStr))
			// TODO: WithTimeout-Option zu opts hinzufügen
			_ = timeout // Diese Zeile bei der Implementierung entfernen
		}
	}

	// TODO: DBConnection mit NewDBConnection unter Verwendung von host und opts erstellen
	// TODO: Verbindungszeichenfolge mit der ConnectionString()-Methode ausgeben

	_ = opts // Diese Zeile bei der Implementierung entfernen
	fmt.Println("TODO: Print connection string here")
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung

Übe selbstständig: Online-Go-Compiler