Introduction : Pourquoi SOLID est important en Go ?
SOLID est un ensemble de cinq principes fondamentaux de la programmation orientée objet qui aident à créer un code flexible, maintenable et extensible. Bien que Go ne soit pas un langage OOP classique (il n'a pas de classes ni d'héritage), ces principes s'appliquent parfaitement grâce aux interfaces, à la composition et au typage strict. Dans cet article, nous allons décomposer chaque principe SOLID avec des exemples en Go, afin que vous puissiez écrire un code de meilleure qualité et plus évolutif.
1. Single Responsibility Principle (SRP) — Principe de responsabilité unique
Le SRP stipule : « Un module ne doit avoir qu'une seule raison de changer ». En Go, cela signifie qu'une fonction, une structure ou un package doit être responsable d'une tâche clairement définie.
Mauvais exemple (violation du SRP) :
type Report struct {
Title string
Body string
}
func (r *Report) Generate() string {
return fmt.Sprintf("<h1>%s</h1><p>%s</p>", r.Title, r.Body)
}
func (r *Report) SaveToFile(filename string) error {
return os.WriteFile(filename, []byte(r.Generate()), 0644)
}
func (r *Report) SendEmail(addr string) error {
// logique d'envoi d'email
return nil
}
Ici, la structure Report est responsable de la génération, de la sauvegarde et de l'envoi — trois raisons différentes de changer.
Bon exemple (respect du SRP) :
type Report struct {
Title string
Body string
}
func (r *Report) Generate() string {
return fmt.Sprintf("<h1>%s</h1><p>%s</p>", r.Title, r.Body)
}
type ReportSaver struct{}
func (rs *ReportSaver) SaveToFile(report *Report, filename string) error {
return os.WriteFile(filename, []byte(report.Generate()), 0644)
}
type EmailSender struct{}
func (es *EmailSender) Send(report *Report, addr string) error {
// logique d'envoi d'email
return nil
}
Maintenant, chaque entité a une seule responsabilité : génération, sauvegarde et envoi.
2. Open/Closed Principle (OCP) — Principe d'ouverture/fermeture
L'OCP affirme : « Les entités logicielles doivent être ouvertes à l'extension, mais fermées à la modification ». En Go, cela est réalisé via les interfaces et la composition.
Mauvais exemple (violation de l'OCP) :
type PaymentProcessor struct{}
func (p *PaymentProcessor) Process(paymentType string, amount float64) {
if paymentType == "credit_card" {
// traitement de la carte de crédit
} else if paymentType == "paypal" {
// traitement PayPal
} else if paymentType == "crypto" {
// traitement de la cryptomonnaie
}
}
Lors de l'ajout d'un nouveau type de paiement, il faudra modifier la méthode Process.
Bon exemple (respect de l'OCP) :
type PaymentMethod interface {
Pay(amount float64) error
}
type CreditCard struct{}
func (c *CreditCard) Pay(amount float64) error {
// logique de paiement par carte
return nil
}
type PayPal struct{}
func (p *PayPal) Pay(amount float64) error {
// logique de paiement via PayPal
return nil
}
type PaymentProcessor struct{}
func (pp *PaymentProcessor) Process(method PaymentMethod, amount float64) error {
return method.Pay(amount)
}
Maintenant, pour ajouter un nouveau moyen de paiement, il suffit d'implémenter l'interface PaymentMethod, sans toucher au code existant.
3. Liskov Substitution Principle (LSP) — Principe de substitution de Barbara Liskov
Le LSP exige : « Les objets d'un programme doivent être remplaçables par des instances de leurs sous-types sans altérer la correction du programme ». En Go, cela concerne l'implémentation des interfaces.
Mauvais exemple (violation du LSP) :
type Bird interface {
Fly() error
}
type Penguin struct{}
func (p *Penguin) Fly() error {
return errors.New("les pingouins ne volent pas")
}
func LetBirdFly(b Bird) {