SOLID-Prinzipien in Go: Fortgeschrittenes Niveau
SOLID – das sind fünf fundamentale Prinzipien des objektorientierten Designs, die helfen, flexiblen, wartbaren und skalierbaren Code zu erstellen. Im Kontext von Go, einer Sprache mit einer einzigartigen Philosophie der Komposition statt Vererbung, erfordert die Anwendung von SOLID einen besonderen Ansatz. In diesem Artikel werden wir jedes Prinzip auf fortgeschrittenem Niveau mit realen Beispielen und für die Go-Community typischen Antipatterns analysieren.
1. Single Responsibility Principle (SRP) in Go
Das Prinzip der einzigen Verantwortung besagt: Eine Klasse (Struktur) sollte nur einen Grund für eine Änderung haben. In Go wird dies oft in Utility-Paketen und "göttlichen Objekten" verletzt.
Antipattern: Struktur mit mehreren Zuständigkeiten
type UserService struct { db *sql.DB cache *redis.Client logger *log.Logger}
func (s *UserService) CreateUser(ctx context.Context, u User) error { // 1. Validierung // 2. Passwort-Hashing // 3. Speichern in der DB // 4. E-Mail senden // 5. Logging return nil}Lösung: Teilen Sie die Verantwortlichkeiten in separate Dienste auf und verwenden Sie Komposition.
type UserValidator interface { Validate(u User) error}
type PasswordHasher interface { Hash(password string) (string, error)}
type UserRepository interface { Save(ctx context.Context, u User) error}
type EmailSender interface { SendWelcomeEmail(ctx context.Context, email string) error}
type UserService struct { validator UserValidator hasher PasswordHasher repo UserRepository emailSender EmailSender logger Logger}Jede Komponente hat jetzt nur einen Grund für eine Änderung, was das Testen und die Wartung vereinfacht.
2. Open-Closed Principle (OCP) durch Interfaces
Das Prinzip der Offenheit/Geschlossenheit: Entitäten sollten für Erweiterungen offen, aber für Modifikationen geschlossen sein. In Go wird dies durch Interfaces und Dependency Injection erreicht.
Schlechtes Beispiel: Feste Kopplung
type PaymentProcessor struct { paypalAPI *PayPalAPI}
func (p *PaymentProcessor) Process(amount float64) error { return p.paypalAPI.Charge(amount)}Wenn Stripe hinzugefügt werden soll, muss der Code geändert werden.
Richtiger Ansatz: Interface + Strategie
type PaymentGateway interface { Charge(amount float64) error Refund(transactionID string) error}
type PayPalGateway struct{ /* ... */ }type StripeGateway struct{ /* ... */ }
func (p *PayPalGateway) Charge(amount float64) error { /* ... */ }func (s *StripeGateway) Charge(amount float64) error { /* ... */ }
type PaymentService struct { gateway PaymentGateway}
func NewPaymentService(gw PaymentGateway) *PaymentService { return &PaymentService{gateway: gw}}Jetzt können neue Zahlungsgateways hinzugefügt werden, ohne PaymentService zu ändern.
3. Liskov Substitution Principle (LSP) im Kontext von Go
Das Substitutionsprinzip von Barbara Liskov: Objekte in einem Programm sollten durch Instanzen ihrer Subtypen ersetzt werden können, ohne die Korrektheit des Programms zu beeinträchtigen. In Go, wo es keine klassische Vererbung gibt, zeigt sich LSP durch Interface-Verträge.
Typische Verletzung: Interface mit überflüssigen Methoden
type Bird interface { Fly() error Swim() error}
type Penguin struct{}
func (p *Penguin) Fly() error { return errors.New("penguins can't fly")}
func (p *Penguin) Swim() error { return nil}Hier kann Penguin Fly() nicht korrekt implementieren, was LSP verletzt.
Lösung: Aufteilung der Interfaces
type Flyer interface { Fly() error}
type Swimmer interface { S