Das Options-Pattern in Go/Golang in 4 einfachen Schritten umsetzen

Das Options-Pattern in Go/Golang in 4 einfachen Schritten umsetzen

Das Options-Pattern in der Go-Programmierung ist eine Umsetzung des Open-Close Principals und dient dazu die Auswirkungen von Code-Änderungen bzw. Erweiterungen zu minimieren und damit Systeme leicht erweiterbar zu machen.

“Modules should be both open (for extension) and closed (for modification).” - Bertrand Meyer

In diesem Artikel werden wir uns eingehend mit dem Options-Pattern in Golang befassen und wie es zur Verbesserung der Codelesbarkeit und -flexibilität beitragen kann.

Was ist das Options-Pattern?

Das Options-Pattern ist eine Designpraxis, die es ermöglicht, Funktionen mit variabler Anzahl von Parametern zu erstellen, ohne die Lesbarkeit des Codes zu beeinträchtigen. In Golang wird diese Technik oft verwendet, um “Konstruktoren” bzw. Factory-Methoden von Structs oder Funktionen mit optionalen Parametern zu implementieren.

Vorteile des Option-Patterns in Golang

Verbesserte Lesbarkeit

Durch die Verwendung des Option-Patterns wird der Code klarer und leichter verständlich. Benötigt eine Funktion z. B. mehrere Parameter, die als String-Werte übergeben werden müssen, wird eine Parameterliste schnell unübersichtlich. Mit dem Options-Pattern werden die Parameter typisiert und Entwickler können auf einen Blick sehen, welche Optionen bzw. Parameter für eine Funktion verfügbar sind, ohne den gesamten Quellcode durchsuchen zu müssen.

String-Beisiel:

func BuildMyObjekt(param1, param2, param3, param3 string) MyObj {
    //doSth
}

//Aufruf irgendwo
BuildObject("server1", ":8090", "server3", "4231")

Ohne das Options-Pattern sieht man recht schön, dass man im Quellcode die Semantik der einzelnen Parameter nur schwer erkennen kann.

Options-Pattern-Beispiel:

func BuildMyObjekt(opts ...Option) MyObj {
    //doSth
}

//Aufruf irgendwo
BuildObject(WithConnectServer("server1"), 
    WithConnectServerBinding(":8090),
    WithXYServer("server3"),
    WithMaxConnections("4231))

Mit dem Options-Pattern ist der Code deutlich lesbarer.

Flexibilität in der Anwendung

Das Optionsmuster ermöglicht es Entwicklern, nur die benötigten Parameter anzugeben und den Rest mit Standardwerten zu füllen. Dadurch wird die Anwendung flexibler und anpassbar, ohne die Integrität des Codes zu beeinträchtigen.

Im obigen Beispiel müssen in der ersten Variante immer alle Werte angeben werden. Beim Einsatz des Options-Pattern hingegen, hat man die Möglichkeit auch nur die Werte zu übergeben, die für den aktuellen Einsatz benötigt werden.

//Aufruf irgendwo
BuildObject(WithXYServer("server3"),
    WithMaxConnections("4231))

Spätere Erweiterungen bzw. das Hinzufügen von weiteren Optionen ist ebenfalls ohne Probleme möglich. Die API bleibt stabil.

Implementierung des Optionsmusters in Golang

Das Options-Pattern in Golang wird mit Hilfe von Function-Types und unter Anwendung von Closures umgesetzt.

  • Schritt 1: Über einen Function-Type wird eine Funktion definiert , die das zu konfigurierende Objekt als Parameter übergeben bekommt. Im folgendem Beispiel soll der Typ *http.Server konfiguriert werden.

    type Option func(server *http. Server)
    
  • Schritt 2: Die einzelnen Konfigurationen werden anschließend über Factory-Methoden mit Closures umgesetzt. Die Funktionen liefern hierbei konkrete Implementierungen des definierten Function-Types und konfigurieren das übergebene “Ziel-Objekt”.

    func WithBinding ( binding string ) Option {
        return func( server *http. Server ) {
            server .Addr = binding
        }
    }
    
  • Schitt 3: In einer entsprechenden Factory-Function bzw. eines Konstruktor werden die übergebenen Optionen in einr Schleife auf das zu konfigurierende Objekt angewendet. Standardwerte können hier ebenfalls definiert und überschrieben werden.

    func NewServer(opts ...ServerOption) *ServerConfig {
    // Standardwerte für die Konfiguration festlegen
    config := &ServerConfig{
        Host:    "localhost",
        Port:    8080,
        Timeout: 30 * time.Second,
    }
    
    // Optionen anwenden
    for _, opt := range opts {
        opt(config)
    }
    
    return config
    }
    
  • Schritt 4: Ein Client dieser API kann somit die einzelnen Konfigurationen über Parameterübergabe vornehmen und weitere Optionen können später ohne API-Anpassungen aufgenommen werden.

    server := NewServer (WithBinding (":8080 "))
    

Hier ist das komplette Beispiel:

type Option func(server *http.Server)

func WithBinding(binding string) Option {
	return func(server *http.Server) {
		server.Addr = binding
	}
}

func WithReadTimeout(readTimeout time.Duration) Option {
	return func(server *http.Server) {
		server.ReadTimeout = readTimeout
	}
}

func NewServer(opts ...Option) *http.Server {

	server := &http.Server{}
	for _, opt := range opts {
		opt(server)
	}

	return server

}

Dieses Beispiel ist übrigens Bestandteil unseres Golang Softwaredesign Workshops

17.11.2023

 

Der Author auf LinkedIn: Kristian Köhler und Mastodon: @kkoehler@mastodontech.de

Kennen Sie schon das Buch zum Thema?

Der praktische Soforteinstieg für Developer und Softwarearchitekten, die direkt mit Go produktiv werden wollen.

  • Von den Sprachgrundlagen bis zur Qualitätssicherung
  • Architekturstil verstehen und direkt anwenden
  • Idiomatic Go, gRPC, Go Cloud Development Kit
  • Cloud-native Anwendungen erstellen
Microservices mit Go Buch

zur Buchseite beim Rheinwerk Verlag Rheinwerk Computing, ISBN 978-3-8362-7559-0 (als PDF, EPUB, MOBI und Papier)

Kontakt

Source Fellows GmbH

Source Fellows GmbH Logo

Lerchenstraße 31

772762 Reutlingen

Telefon: (0049) 07121 6969 802

E-Mail: info@source-fellows.com