Principes SOLID en Go : niveau avancé
\n\nSOLID est un ensemble de cinq principes fondamentaux de conception orientée objet qui aident à créer un code flexible, maintenable et évolutif. Dans le contexte de Go, un langage avec une philosophie unique de composition plutôt que d'héritage, l'application de SOLID nécessite une approche particulière. Dans cet article, nous analyserons chaque principe à un niveau avancé, avec des exemples concrets et des anti-patrons caractéristiques de la communauté Go.
\n\n1. Single Responsibility Principle (SRP) en Go
\n\nLe principe de responsabilité unique stipule : une classe (structure) ne doit avoir qu'une seule raison de changer. En Go, cela est souvent violé dans les packages utilitaires et les "objets divins".
\n\nAnti-patron : structure avec de multiples responsabilités
\n\ntype UserService struct {\n db *sql.DB\n cache *redis.Client\n logger *log.Logger\n}\n\nfunc (s *UserService) CreateUser(ctx context.Context, u User) error {\n // 1. Validation\n // 2. Hachage du mot de passe\n // 3. Sauvegarde en base de données\n // 4. Envoi d'email\n // 5. Journalisation\n return nil\n}\n\nSolution : divisez les responsabilités en services séparés et utilisez la composition.
\n\ntype UserValidator interface {\n Validate(u User) error\n}\n\ntype PasswordHasher interface {\n Hash(password string) (string, error)\n}\n\ntype UserRepository interface {\n Save(ctx context.Context, u User) error\n}\n\ntype EmailSender interface {\n SendWelcomeEmail(ctx context.Context, email string) error\n}\n\ntype UserService struct {\n validator UserValidator\n hasher PasswordHasher\n repo UserRepository\n emailSender EmailSender\n logger Logger\n}\n\nChaque composant a désormais une seule raison de changer, ce qui simplifie les tests et la maintenance.
\n\n2. Open-Closed Principle (OCP) via les interfaces
\n\nLe principe d'ouverture/fermeture : les entités doivent être ouvertes à l'extension, mais fermées à la modification. En Go, cela est réalisé via les interfaces et l'injection de dépendances.
\n\nMauvais exemple : couplage fort
\n\ntype PaymentProcessor struct {\n paypalAPI *PayPalAPI\n}\n\nfunc (p *PaymentProcessor) Process(amount float64) error {\n return p.paypalAPI.Charge(amount)\n}\n\nSi vous devez ajouter Stripe, vous devrez modifier le code.
\n\nApproche correcte : interface + stratégie
\n\ntype PaymentGateway interface {\n Charge(amount float64) error\n Refund(transactionID string) error\n}\n\ntype PayPalGateway struct{ /* ... */ }\ntype StripeGateway struct{ /* ... */ }\n\nfunc (p *PayPalGateway) Charge(amount float64) error { /* ... */ }\nfunc (s *StripeGateway) Charge(amount float64) error { /* ... */ }\n\ntype PaymentService struct {\n gateway PaymentGateway\n}\n\nfunc NewPaymentService(gw PaymentGateway) *PaymentService {\n return &PaymentService{gateway: gw}\n}\n\nVous pouvez désormais ajouter de nouvelles passerelles de paiement sans modifier PaymentService.
3. Liskov Substitution Principle (LSP) dans le contexte de Go
\n\nLe principe de substitution de Barbara Liskov : 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, où il n'y a pas d'héritage classique, le LSP se manifeste à travers les contrats d'interface.
\n\nViolation typique : interface avec des méthodes redondantes
\n\ntype Bird interface {\n Fly() error\n Swim() error\n}\n\ntype Penguin struct{}\n\nfunc (p *Penguin) Fly() error {\n return errors.New("penguins can't fly")\n}\n\nfunc (p *Penguin) Swim() error {\n return nil\n}\n\nIci, Penguin ne peut pas implémenter correctement Fly(), ce qui viole le LSP.
Solution : séparation des interfaces
\n\ntype Flyer interface {\n Fly() error\n}\n\ntype Swimmer interface {\n S