Patron Strategy
Fait partie de la section Programmation Orientée Objet du Journey GO de Coddy — leçon 90 sur 107.
Le patron Stratégie (Strategy pattern) vous permet de définir une famille d'algorithmes, d'encapsuler chacun d'eux et de les rendre interchangeables au moment de l'exécution. Contrairement au patron Observateur où plusieurs objets réagissent à un seul événement, le patron Stratégie permet à un seul objet de basculer dynamiquement entre différents comportements.
En Go, nous implémentons le pattern Strategy en définissant une interface pour l'algorithme et en injectant différentes implémentations dans une structure de contexte (context struct) :
type PaymentStrategy interface {
Pay(amount float64) string
}
type CreditCard struct{}
func (c CreditCard) Pay(amount float64) string {
return fmt.Sprintf("Paid %.2f via Credit Card", amount)
}
type PayPal struct{}
func (p PayPal) Pay(amount float64) string {
return fmt.Sprintf("Paid %.2f via PayPal", amount)
}La structure de contexte détient une référence à l'interface de stratégie, permettant à l'algorithme d'être interchangé sans modifier le code du contexte :
type Checkout struct {
strategy PaymentStrategy
}
func (c *Checkout) SetStrategy(s PaymentStrategy) {
c.strategy = s
}
func (c *Checkout) ProcessPayment(amount float64) string {
return c.strategy.Pay(amount)
}Maintenant, vous pouvez changer la méthode de paiement au moment de l'exécution :
checkout := &Checkout{}
checkout.SetStrategy(CreditCard{})
fmt.Println(checkout.ProcessPayment(100.00)) // Payé 100.00 via Carte de Crédit
checkout.SetStrategy(PayPal{})
fmt.Println(checkout.ProcessPayment(50.00)) // Payé 50.00 via PayPalLe modèle Strategy est idéal lorsque vous disposez de plusieurs façons d'effectuer une opération et que vous devez en sélectionner une en fonction de la saisie de l'utilisateur, de la configuration ou des conditions d'exécution. Les cas d'utilisation courants incluent les algorithmes de tri, les méthodes de compression et les règles de validation.
Défi
FacileConstruisons un système de formatage de texte en utilisant le patron de conception Strategy ! Vous allez créer différentes stratégies de formatage qui peuvent être interchangées au moment de l'exécution, permettant au même processeur de texte de produire différents styles de sortie sans modifier son code de base.
Vous organiserez votre code sur trois fichiers :
formatter.go: Définissez votre interface de stratégie et les stratégies de formatage concrètes.Créez une interface
TextFormatteravec une méthodeFormat(text string) stringqui transforme le texte selon les règles de la stratégie.Implémentez trois stratégies de formatage :
UppercaseFormatter— convertit le texte en majusculesSnakeCaseFormatter— remplace les espaces par des underscores et met le texte en minusculesTitleFormatter— met en majuscule la première lettre de chaque mot (les mots sont séparés par des espaces)
processor.go: Créez votre structure de contexte qui utilise la stratégie de formatage.Construisez une structure
TextProcessorqui contient une stratégieTextFormatter. Ajoutez ces méthodes :SetFormatter(f TextFormatter)pour changer la stratégie de formatage activeProcess(text string) stringqui applique le formateur actuel au texte
Créez également un constructeur
NewTextProcessor()qui retourne un processeur sans formateur initial défini.main.go: Démontrez le changement de stratégies au moment de l'exécution.Lisez un texte, puis lisez un nombre d'opérations de formatage. Pour chaque opération, lisez le type de format (
upper,snake, outitle), définissez le formateur approprié sur votre processeur, traitez le texte original et affichez le résultat.
Les entrées suivantes seront fournies :
- Ligne 1 : Le texte à formater
- Ligne 2 : Le nombre d'opérations de formatage
- Lignes suivantes : Le type de format pour chaque opération
Par exemple, avec :
hello world today
3
upper
snake
titleVotre sortie devrait être :
HELLO WORLD TODAY
hello_world_today
Hello World TodayEt avec :
Go Programming Language
2
snake
upperVotre sortie devrait être :
go_programming_language
GO PROGRAMMING LANGUAGEEt avec :
design patterns are useful
1
titleVotre sortie devrait être :
Design Patterns Are UsefulRemarquez comment le même TextProcessor produit des sorties complètement différentes simplement en changeant sa stratégie de formateur. Le processeur ne sait pas et ne se soucie pas du formateur spécifique qu'il utilise — il délègue simplement à la stratégie qui est actuellement définie !
Aide-mémoire
Le pattern Strategy définit une famille d'algorithmes, encapsule chacun d'eux et les rend interchangeables au moment de l'exécution. Il permet à un seul objet de basculer dynamiquement entre différents comportements.
Définissez une interface pour l'algorithme :
type PaymentStrategy interface {
Pay(amount float64) string
}Implémentez des stratégies concrètes :
type CreditCard struct{}
func (c CreditCard) Pay(amount float64) string {
return fmt.Sprintf("Paid %.2f via Credit Card", amount)
}
type PayPal struct{}
func (p PayPal) Pay(amount float64) string {
return fmt.Sprintf("Paid %.2f via PayPal", amount)
}Créez une structure de contexte qui contient une référence à l'interface de stratégie :
type Checkout struct {
strategy PaymentStrategy
}
func (c *Checkout) SetStrategy(s PaymentStrategy) {
c.strategy = s
}
func (c *Checkout) ProcessPayment(amount float64) string {
return c.strategy.Pay(amount)
}Changez de stratégie au moment de l'exécution :
checkout := &Checkout{}
checkout.SetStrategy(CreditCard{})
fmt.Println(checkout.ProcessPayment(100.00)) // Payé 100.00 via Credit Card
checkout.SetStrategy(PayPal{})
fmt.Println(checkout.ProcessPayment(50.00)) // Payé 50.00 via PayPalLe pattern Strategy est idéal lorsque vous avez plusieurs façons d'effectuer une opération et que vous devez en sélectionner une en fonction de la saisie de l'utilisateur, de la configuration ou des conditions d'exécution.
Essayez vous-même
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Lire le texte à formater
text, _ := reader.ReadString('\n')
text = strings.TrimSpace(text)
// Lire le nombre d'opérations
countStr, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countStr))
// Créer un nouveau processeur de texte
processor := NewTextProcessor()
// Traiter chaque opération de formatage
for i := 0; i < count; i++ {
formatType, _ := reader.ReadString('\n')
formatType = strings.TrimSpace(formatType)
// TODO: Selon le formatType ("upper", "snake", ou "title"),
// définir le formateur approprié sur le processeur
// et afficher le texte traité
}
}
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Programmation Orientée Objet
1Fondamentaux de la POO en Go
Fichiers externesEspace de travail et Modules GoPackages et ImportsNoms exportés vs non exportésIntroduction à la POO en GoLes Structs comme ClassesDéfinir des méthodes sur les StructsReceveurs par pointeur vs par valeurInitialisation de StructsFonctions constructeursRécapitulatif - Calculatrice simple4Interfaces
Introduction aux interfacesImplémentation impliciteL'interface comme contratInterface vide (any)Assertion de typeType SwitchComposition d'interfacesInterfaces Stringer & ErrorRécapitulatif - Calculateur de formes7Encapsulation
Champs exportés vs non exportésEncapsulation au niveau du packageMéthodes Getter & SetterMasquage d'informations en GoRécapitulatif - Dossiers étudiants10Génériques (Go 1.18+)
Introduction aux génériquesParamètres de typeContraintes de typeStructures génériquesContournement pour les méthodes génériquesRécapitulatif - Collections génériques13Patrons de conception, Partie 1
Introduction aux Design PatternsPatron SingletonPatron FactoryPatron Abstract FactoryPatron ObserverPatron Strategy2Exploration approfondie des Types et Structs
Types de base et compositesDéfinitions de types personnalisésTags de StructStructs anonymesStructs imbriquéesValeurs par défaut et Zero ValuesRécapitulatif - Carnet de contacts5Composition plutôt qu'héritage
Pourquoi Go n'a pas d'héritageLes bases de l'imbrication de structsPromotion de méthodesImbriquer plusieurs structsImbrication vs AgrégationMasquage de méthodes imbriquéesRécapitulatif - Hiérarchie des employés8Gestion des erreurs & POO
L'interface errorTypes d'erreurs personnalisésError Wrapping (fmt.Errorf)Erreurs sentinelleserrors.Is() et errors.As()Panic, Defer et RecoverRécapitulatif - File Parser11Bibliothèque standard et POO
io.Reader & io.WriterL'interface sort.InterfaceL'interface fmt.Stringerencoding/json avec les StructsL'interface http.HandlerRécapitulatif - Modèles d'API REST14Patrons de conception, Partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Template MethodPatron ÉtatPatron CompositeMiddleware en tant que Décorateur3Pointeurs et Mémoire
Bases des pointeurs en GoPointeurs vers des structsPassage par valeur vs référenceLa fonction new()Garbage Collection en GoRécapitulatif - Constructeur de liste chaînée6Le polymorphisme en Go
Le polymorphisme via les interfacesLe Duck Typing en GoRègles de satisfaction des interfacesCollections polymorphesInjection de dépendancesRécapitulatif - Processeur de paiement9Concurrence et POO
Bases des GoroutinesChannels et CommunicationChannels avec buffer vs sans bufferL'instruction Selectsync.Mutex et sync.RWMutexsync.WaitGroupConception de structures Thread-SafeRécapitulatif - Worker Pool