SOLID-Prinzipien in Go: Theorie und Praxis von A bis Z
SOLID ist ein Akronym für fünf fundamentale Prinzipien des objektorientierten Designs, formuliert von Robert C. Martin (Uncle Bob). Obwohl Go keine klassische objektorientierte Sprache ist (es gibt keine Klassen und Vererbung im herkömmlichen Sinne), sind diese Prinzipien anwendbar und entscheidend für die Erstellung von qualitativ hochwertigem, wartbarem und skalierbarem Code. In diesem Artikel werden wir jedes SOLID-Prinzip anhand von Beispielen aus der realen Go-Praxis analysieren.
1. Single Responsibility Principle (SRP) – Prinzip der einzigen Verantwortung
Kernaussage: Jedes Modul, jede Struktur oder Funktion sollte nur einen einzigen Grund für eine Änderung haben. Mit anderen Worten: Sie sollten für eine klar definierte Aufgabe verantwortlich sein.
Problem: Oft legen Entwickler die gesamte Logik in eine einzige Struktur: Validierung, Speicherung in der Datenbank, E-Mail-Versand, Antwortformatierung. Dies führt zu „Gott-Objekten“ (God Objects), die schwer zu testen und zu ändern sind.
Lösung in Go: Teilen Sie die Verantwortlichkeiten auf verschiedene Strukturen und Pakete auf.
// ❌ Schlecht: Eine Struktur macht allestype UserService struct { db *sql.DB mailer *Mailer}
func (s *UserService) CreateUser(name, email string) error { // 1. Validierung if !strings.Contains(email, "@") { return errors.New("ungültige E-Mail") } // 2. Speicherung in der Datenbank _, err := s.db.Exec("INSERT INTO users (name, email) VALUES ($1, $2)", name, email) if err != nil { return err } // 3. Versand der Begrüßungs-E-Mail return s.mailer.Send(email, "Willkommen!", "Vielen Dank für Ihre Registrierung")}
// ✅ Gut: Aufteilung der Verantwortlichkeitentype UserValidator struct{}func (v *UserValidator) Validate(email string) error { if !strings.Contains(email, "@") { return errors.New("ungültige E-Mail") } return nil}
type UserRepository struct { db *sql.DB}func (r *UserRepository) Save(name, email string) error { _, err := r.db.Exec("INSERT INTO users (name, email) VALUES ($1, $2)", name, email) return err}
type WelcomeMailer struct { mailer *Mailer}func (m *WelcomeMailer) Send(email string) error { return m.mailer.Send(email, "Willkommen!", "Vielen Dank für Ihre Registrierung")}
type UserService struct { validator *UserValidator repo *UserRepository mailer *WelcomeMailer}
func (s *UserService) CreateUser(name, email string) error { if err := s.validator.Validate(email); err != nil { return err } if err := s.repo.Save(name, email); err != nil { return err } return s.mailer.Send(email)}Praktischer Tipp: Wenn Sie nicht kurz beschreiben können, was Ihre Funktion oder Methode tut, ohne die Konjunktion „und“ zu verwenden, ist das SRP wahrscheinlich verletzt.
2. Open-Closed Principle (OCP) – Prinzip der Offenheit/Abgeschlossenheit
Kernaussage: Softwareentitäten (Strukturen, Funktionen, Pakete) sollten offen für Erweiterungen, aber geschlossen für Änderungen sein. Das bedeutet, dass Sie neues Verhalten hinzufügen können sollten, ohne vorhandenen Code zu ändern.
Lösung in Go: Verwenden Sie Interfaces, um Verhalten zu abstrahieren.
// ❌ Schlecht: Beim Hinzufügen eines neuen Zahlungstyps muss die Funktion ProcessPayment geändert werdentype PaymentProcessor struct{}
func (p *PaymentProcessor) Process(paymentType string, amount float64) { if paymentType == "credit_card" { // Logik für Kreditkarte } else if paymentType == "paypal" { // Logik für PayPal } // Beim Hinzufügen von "crypto" – muss ein neues else if hinzugefügt werden}
// ✅ Gut: Das Interface ermöglicht die Erweiterung der Funktionalität ohne Codeänderungtype PaymentMethod interface { Pay(amount float64) error}
type CreditCard struct{}func