Modèles de conception en Go : pas une « religion », mais un outil
\n\nQuand j’ai commencé à coder en Go, j’avais l’impression que tous ces « gang of four » et leurs 23 patrons étaient quelque chose venant du monde Java et C++. Go est si simple et concis — pourquoi aurions-nous besoin de ces complexités ? Mais plus je plongeais dans des projets réels (microservices, utilitaires CLI, gestionnaires d’API), plus je me surprenais à penser : « Ici, une méthode factory serait utile » ou « Cette montagne de if-else pourrait être remplacée par une stratégie ».
\n\nLes modèles de conception en Go ne sont ni un dogme ni une tentative d’adapter la POO à un langage fonctionnel. Ce sont des solutions éprouvées à des problèmes typiques. Ils aident à rendre le code testable, flexible et compréhensible pour les collègues. Et oui, Go laisse sa marque : en raison de l’absence d’héritage classique et de generics (avant la version 1.18), certains patrons se transforment, tandis que d’autres deviennent redondants.
\n\nDans cet article, nous n’allons pas énumérer les 23 patrons. Au lieu de cela, nous allons analyser les 5 modèles les plus demandés en Golang qui seront réellement utiles dans des projets concrets. Vous verrez quand les appliquer, et quand les éviter à tout prix.
\n\nPatron Singleton en Go : pas juste un singleton, mais un singleton à la go
\n\nSingleton garantit qu’une structure n’aura qu’une seule instance. En Go, on l’utilise souvent pour les configurations, les pools de connexions ou les loggers. Mais il y a un détail : Go n’a pas de classes statiques, mais il a sync.Once — l’outil idéal pour une initialisation paresseuse.
Problème sans Singleton
\nImaginez que vous ayez une structure Config qui lit les paramètres depuis un fichier. Si chaque paquet crée sa propre instance, vous lirez le fichier 10 fois (lent) ou vous aurez une race condition.
Solution avec Singleton
\npackage config\n\nimport (\n "sync"\n "encoding/json"\n "os"\n)\n\ntype Config struct {\n Port int `json:"port"`\n Env string `json:"env"`\n}\n\nvar (\n instance *Config\n once sync.Once\n)\n\nfunc GetConfig() *Config {\n once.Do(func() {\n file, _ := os.Open("config.json")\n defer file.Close()\n instance = &Config{}\n json.NewDecoder(file).Decode(instance)\n })\n return instance\n}\n\nQuand l’utiliser : quand une unicité garantie de l’instance est nécessaire (configurations, caches, fabriques).
\nQuand ne pas l’utiliser : pour tout et n’importe quoi. Le Singleton est un état global qui complique les tests. Si vous pouvez passer la dépendance via un constructeur, faites-le.
\n\nPatron Factory : quand la création d’objets devient complexe
\n\nFactory Method et Abstract Factory concernent l’encapsulation de la logique de création d’objets. En Go, c’est particulièrement pertinent car nous n’avons pas de constructeurs au sens classique. À la place, nous utilisons des fonctions constructeurs avec le préfixe New.
Exemple : création de notifications
\nSupposons que votre système envoie des notifications par Email, SMS et Push. Au lieu de disperser la logique de sélection dans le code, nous utilisons une fabrique.
\n\npackage notification\n\nimport "fmt"\n\ntype Notifier interface {\n Send(message string) error\n}\n\ntype EmailNotifier struct{}\n\nfunc (e EmailNotifier) Send(message string) error {\n fmt.Println("Envoi d'email :", message)\n return nil\n}\n\ntype SMSNotifier struct{}\n\nfunc (s SMSNotifier) Send(message string) error {\n fmt.Println("Envoi de SMS :", message)\n return nil\n}\n\nfunc NewNotifier(typ string) Notifier {\n switch typ {\n case "email":\n return EmailNotifier{}\n case "sms":\n return SMSNotifier{}\n default:\n return nil\n }\n}\n\nQuand l’utiliser : quand la logique de création est complexe (dépend du type, de la version, de la configuration).
\nQuand ne pas l’utiliser : si vous n’avez qu’un seul type d’objet. Écrivez simplement MyStruct{} ou NewMyStruct() sans fabrique.