SOLID-Prinzipien in Go: Theorie und Praxis
SOLID ist ein Akronym für fünf grundlegende Prinzipien des objektorientierten Designs, die helfen, flexiblen, wartbaren und skalierbaren Code zu erstellen. Obwohl Go keine klassische OOP-Sprache ist (es gibt keine Klassenvererbung), ermöglichen seine Interfaces, Komposition und strenge Typisierung die erfolgreiche Anwendung von SOLID. In diesem Artikel werden wir jedes Prinzip anhand von Beispielen aus der realen Welt in der Sprache Go durchgehen.
1. Single Responsibility Principle (SRP)
Theorie: Jeder Typ (Struktur, Funktion, Interface) sollte nur einen einzigen Grund für eine Änderung haben. Mit anderen Worten, er ist nur für einen Teil der Funktionalität des Programms verantwortlich.
Praxis in Go: Ein häufiger Fehler ist die Erstellung von "göttlichen" Strukturen, die sich sowohl um die Geschäftslogik, als auch um das Logging und das Senden von Benachrichtigungen kümmern.
Schlechtes Beispiel (Verletzung des SRP):
type OrderService struct{}
func (s *OrderService) Create(order Order) error { // 1. Speichern in der DB // 2. E-Mail senden // 3. Logging return nil}Gutes Beispiel (Einhaltung des SRP):
type OrderRepository interface { Save(order Order) error}
type EmailService interface { Send(to string, subject string, body string) error}
type Logger interface { Info(msg string)}
type OrderService struct { repo OrderRepository email EmailService log Logger}
func (s *OrderService) Create(order Order) error { if err := s.repo.Save(order); err != nil { s.log.Info("Failed to save order") return err } if err := s.email.Send(order.Email, "Order Confirmed", "Your order is placed"); err != nil { s.log.Info("Failed to send email") } s.log.Info("Order created successfully") return nil}Jetzt ist jede Struktur nur für ihre eigene Aufgabe verantwortlich, und eine Änderung der E-Mail-Logik hat keine Auswirkungen auf das Speichern von Bestellungen.
2. Open/Closed Principle (OCP)
Theorie: Klassen (Typen) sollten offen für Erweiterungen, aber geschlossen für Modifikationen sein. Das bedeutet, Sie sollten in der Lage sein, neue Funktionalität hinzuzufügen, ohne vorhandenen Code zu ändern.
Praxis in Go: Verwenden Sie Interfaces und Komposition, um neue Implementierungen hinzuzufügen, ohne die Hauptlogik zu ändern.
Beispiel: Angenommen, wir haben ein System zur Berechnung der Versandkosten. Ursprünglich gibt es nur den "Standardversand", aber wir müssen einen "Expressversand" hinzufügen.
// Interface definierentype ShippingCostCalculator interface { Calculate(order Order) float64}
// Standardimplementierungtype StandardShipping struct{}
func (s StandardShipping) Calculate(order Order) float64 { return 5.99}
// Neue Implementierung ohne Änderung des obigen Codestype ExpressShipping struct{}
func (e ExpressShipping) Calculate(order Order) float64 { return 15.99}
// Verwendungfunc ProcessOrder(order Order, calculator ShippingCostCalculator) { cost := calculator.Calculate(order) fmt.Printf("Shipping cost: %.2f", cost)}Wir ändern ProcessOrder nicht – wir übergeben einfach eine neue Instanz von ExpressShipping. Das ist OCP.
3. Liskov Substitution Principle (LSP)
Theorie: Objekte in einem Programm sollten durch Instanzen ihrer Subtypen ersetzbar sein, ohne die Korrektheit des Programms zu beeinträchtigen. In Go bedeutet dies, dass, wenn eine Funktion ein Interface erwartet, jede Implementierung dieses Interfaces korrekt funktionieren muss.
Praxis in Go: Achten Sie darauf, dass Ihre Interface-Implementierungen nicht die Erwartungen des aufrufenden Codes verletzen. Wenn das Interface beispielsweise annimmt, dass die Methode GetName() immer ein