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.
Aufgabe
EinfachLass 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*DBConnectionverändert.Erstelle diese Optionsfunktionen, die eine
Optionzurückgeben:WithPort(port int)WithCredentials(username, password string)WithMaxConnections(max int)WithTimeout(seconds int)
Erstelle einen Konstruktor
NewDBConnection(host string, opts ...Option), der eine*DBConnectionzurückgibt. Lege diese Standardwerte fest: port5432, username"admin", password"secret", maxConnections10, timeout30. 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 Portnummercredentials, gefolgt von Benutzername und Passwort (zwei Zeilen)maxconn, gefolgt von der Anzahl der maximalen Verbindungentimeout, gefolgt vom Timeout in Sekunden
Erstelle eine
DBConnectionmit 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
0Deine 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
mypasswordDeine 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
60Deine 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")
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP in Go
Externe DateienGo-Workspace & ModulePackages & ImportsExportierte vs. nicht exportierte NamenEinführung in OOP mit GoStructs als KlassenMethoden für Structs definierenPointer- vs. Value-ReceiverStruct-InitialisierungKonstruktorfunktionenRückblick – Einfacher Taschenrechner4Schnittstellen
Einführung in SchnittstellenImplizite ImplementierungSchnittstelle als VertragLeere Schnittstelle (any)TypzusicherungTypwechselZusammensetzung von SchnittstellenStringer- und Error-SchnittstellenRückblick – Formenrechner7Kapselung
Exportierte vs. nicht exportierte FelderKapselung auf PaketebeneGetter- und Setter-MethodenInformationsverbergung in GoRückblick – Studierendendatensätze10Generics (Go 1.18+)
Einführung in GenericsTypparameterTypbeschränkungenGenerische StrukturenWorkaround für generische MethodenZusammenfassung – Generische Sammlung2Typen & Structs im Detail
Grundlegende & zusammengesetzte TypenBenutzerdefinierte TypdefinitionenStruct-TagsAnonyme StructsVerschachtelte StructsNullwerte & StandardwerteRückblick – Kontaktbuch5Komposition statt Vererbung
Warum Go keine Vererbung hatGrundlagen der Struct-EinbettungMethoden-PromotionMehrere Structs einbettenEinbettung vs. AggregationVerbergen eingebetteter MethodenRückblick – Mitarbeiterhierarchie8Fehlerbehandlung & OOP
Das Error-InterfaceBenutzerdefinierte FehlertypenError-Wrapping (fmt.Errorf)Sentinel-Fehlererrors.Is() und errors.As()Panic, Defer und RecoverRückblick – Dateiparser3Zeiger & Speicher
Grundlagen von Zeigern in GoZeiger auf StructsÜbergabe per Wert vs. ReferenzDie Funktion new()Garbage Collection in GoRückblick – Verkettete Liste erstellen6Polymorphismus in Go
Polymorphismus über InterfacesDuck-Typing in GoRegeln zur Interface-ErfüllungPolymorphe SammlungenDependency InjectionZusammenfassung – Zahlungsprozessor9Konkurrenz & OOP
Grundlagen der GoroutinesChannels & KommunikationGepufferte vs. ungepufferte ChannelsSelect-Anweisungsync.Mutex & sync.RWMutexsync.WaitGroupThread-sicheres Struct-DesignRückblick – Worker Pool12Fortgeschrittene OOP-Konzepte
Functional-Options-PatternBuilder-Pattern in GoMethod ChainingTyp-Aliase vs. DefinitionenGrundlagen der ReflectionÜberblick über CodegenerierungÜbe selbstständig: Online-Go-Compiler