Einleitung: Warum Entwurfsmuster in Go wichtig sind
Go (Golang) ist eine moderne, kompilierte Sprache, die bei Google für die Bewältigung von Aufgaben in hochbelasteten Systemen entwickelt wurde. Trotz seiner Einfachheit und Prägnanz erfordert die Entwicklung mit Go ein Verständnis von Entwurfsmustern – bewährten Lösungen für typische Aufgaben. In diesem Artikel werden wir die wichtigsten Muster mit Codebeispielen durchgehen, die Ihnen helfen, saubereren, skalierbareren und wartbareren Code zu schreiben.
Warum ist das wichtig? Go unterstützt keine klassische OOP (Vererbung, Methodenüberladung), bietet aber leistungsstarke Mechanismen – Schnittstellen, Komposition und Goroutinen. Die Muster passen sich diesen Besonderheiten an, was sie besonders effektiv macht.
Erzeugungsmuster (Creational)
Erzeugungsmuster verwalten den Prozess der Objekterstellung. In Go werden sie oft durch Konstruktorfunktionen und Schnittstellen implementiert.
1. Singleton (Einzelstück)
Stellt sicher, dass eine Klasse nur eine einzige Instanz hat, und bietet einen globalen Zugriffspunkt darauf. In Go ist dies besonders relevant für Konfigurationen, Logger und Verbindungspools.
package main
import ( "fmt" "sync")
type Config struct { Port int Host string}
var ( instance *Config once sync.Once)
func GetConfig() *Config { once.Do(func() { instance = &Config{ Port: 8080, Host: "localhost", } }) return instance}
func main() { cfg1 := GetConfig() cfg2 := GetConfig() fmt.Println(cfg1 == cfg2) // true}Erklärung: sync.Once stellt sicher, dass die Initialisierung nur einmal erfolgt, selbst bei gleichzeitigem Zugriff. Dies ist die standardmäßige idiomatische Methode zur Implementierung von Singleton in Go.
2. Factory Method (Fabrikmethode)
Definiert eine Schnittstelle zum Erstellen eines Objekts, ermöglicht es Unterklassen jedoch, den Typ der erstellten Objekte zu ändern. In Go wird es durch Funktionen implementiert, die eine Schnittstelle zurückgeben.
package main
import "fmt"
// Produktschnittstelletype Transport interface { Deliver() string}
// Konkrete Produktetype Truck struct{}func (t *Truck) Deliver() string { return "Lieferung auf dem Landweg" }
type Ship struct{}func (s *Ship) Deliver() string { return "Lieferung auf dem Seeweg" }
// Fabrikmethodefunc CreateTransport(transportType string) Transport { switch transportType { case "land": return &Truck{} case "sea": return &Ship{} default: return nil }}
func main() { transport := CreateTransport("sea") fmt.Println(transport.Deliver()) // Lieferung auf dem Seeweg}3. Builder (Erbauer)
Ermöglicht die schrittweise Erstellung komplexer Objekte. Ideal für Objekte mit vielen optionalen Parametern.
package main
import "fmt"
type Pizza struct { Dough string Sauce string Toppings []string}
type PizzaBuilder struct { dough string sauce string toppings []string}
func NewPizzaBuilder() *PizzaBuilder { return &PizzaBuilder{}}
func (b *PizzaBuilder) SetDough(dough string) *PizzaBuilder { b.dough = dough return b}
func (b *PizzaBuilder) SetSauce(sauce string) *PizzaBuilder { b.sauce = sauce return b}
func (b *PizzaBuilder) AddTopping(topping string) *PizzaBuilder { b.toppings = append(b.toppings, topping) return b}
func (b *PizzaBuilder) Build() *Pizza { return &Pizza{ Dough: b.dough, Sauce: b.sauce, Toppings: b.toppings, }}
func main() { pizza := NewPizzaBuilder(). SetDough("dünn"). SetSauce("Tomatensauce"). AddTopping("Käse"). AddTopping("Pilze"). Build() fmt.Printf("Pizza: %+v", pizza)}