Strategy (Stratégie) est un pattern de conception comportemental qui définit une famille d'algorithmes interchangeables et place chacun d'eux dans une classe séparée (Strategy). Le pattern permet de sélectionner un algorithme à la volée : le code client fonctionne via une interface Strategy commune, et l'implémentation concrète est substituée à l'exécution. Sous iOS, le pattern est implémenté via Protocol + classes de stratégie, sous Android — via Interface + implémentations. Strategy est l'un des 23 patterns GoF, largement utilisé pour le traitement des paiements, la validation, le tri et le filtrage des données. Pour plus de détails — voir la description originale GoF.
Points clés
Strategy est l'un des 23 patterns GoF (Gang of Four), décrit dans le livre « Design Patterns: Elements of Reusable Object-Oriented Software » (1994). Le pattern résout le problème de sélection d'un algorithme à l'exécution. Au lieu d'écrire une seule classe avec de multiples instructions conditionnelles (if-else, switch), Strategy propose d'extraire chaque algorithme dans une classe séparée avec une interface commune. Le contexte (la classe qui utilise la stratégie) contient une référence à l'interface Strategy et délègue l'exécution à la stratégie concrète.
La structure du pattern comprend trois éléments : Context contient une référence à Strategy et appelle sa méthode ; Strategy (interface) déclare une méthode commune pour tous les algorithmes ; ConcreteStrategy implémente l'interface et contient l'algorithme concret. Le client crée la stratégie souhaitée et la passe au contexte via un constructeur, un setter ou un paramètre de méthode. Le contexte ne sait pas quelle stratégie spécifique est exécutée — il travaille uniquement avec l'interface.
| Composant | Rôle | Exemple |
|---|---|---|
| Context | Contient une référence à Strategy | PaymentProcessor, Sorter |
| Strategy | Interface commune pour les algorithmes | Protocol PaymentStrategy |
| ConcreteStrategy | Implémentation concrète de l'algorithme | CardPayment, PayPalPayment |
Principe Open/Closed — le principal avantage de Strategy. Le système est ouvert à l'extension (nouvelle stratégie peut être ajoutée) et fermé à la modification (le code du contexte n'a pas besoin de changer). Sans le pattern, l'ajout d'un nouvel algorithme nécessite de modifier la classe existante, ce qui viole OCP et augmente le risque d'erreurs de régression. Strategy réduit également la taille des classes : au lieu d'une classe de 200 lignes avec un switch-case, vous obtenez 6 classes de 20 lignes chacune.
Strategy en Swift est implémenté via Protocol (interface de stratégie) et des classes ou structures de stratégie. Les protocoles Swift prennent en charge les types associés et les contraintes génériques, offrant une flexibilité lors de la conception de stratégies. Le contexte est généralement une classe ViewModel ou un service qui accepte la stratégie dans init ou via une propriété. Le pattern est largement utilisé dans les projets iOS pour la gestion d'événements, les animations, le formatage des données et les stratégies d'interface utilisateur.
// 1. Protocol Strategy
protocol PaymentStrategy {
func pay(amount: Decimal) async throws -> PaymentResult
}
// 2. Concrete Strategies
struct CardPaymentStrategy: PaymentStrategy {
let cardNumber: String
let cvv: String
func pay(amount: Decimal) async throws -> PaymentResult {
// Envoi de requête à l'API bancaire
return PaymentResult(status: .success, transactionId: "tx_\(UUID())")
}
}
struct PayPalPaymentStrategy: PaymentStrategy {
let email: String
func pay(amount: Decimal) async throws -> PaymentResult {
// Redirection vers PayPal SDK
return PaymentResult(status: .success, transactionId: "pp_\(UUID())")
}
}
// 3. Context
class PaymentProcessor {
private var strategy: PaymentStrategy
init(strategy: PaymentStrategy) {
self.strategy = strategy
}
func setStrategy(_: PaymentStrategy) {
strategy = strategy
}
func processPayment(amount: Decimal) async throws -> PaymentResult {
return try await strategy.pay(amount: amount)
}
}
// Utilisation
let processor = PaymentProcessor(strategy: CardPaymentStrategy(cardNumber: "4111...", cvv: "123"))
let result = try await processor.processPayment(amount: 99.99)
Strategy dans SwiftUI — le pattern s'intègre naturellement avec MVVM. Un ViewModel contient une propriété de stratégie et appelle sa méthode lors d'une action utilisateur. La View SwiftUI reçoit les données via @Published ou @State — la stratégie cache les détails d'implémentation à la View. Par exemple, une stratégie de validation de texte (emailValidator, phoneValidator) est échangée selon le type de champ de saisie. La combinaison de Strategy avec SwiftUI offre de la flexibilité sans hériter de UIKit.
Strategy en Kotlin utilise Interface au niveau du langage et des interfaces fonctionnelles (SAM) pour la simplification. Kotlin prend en charge les lambdas, permettant de passer des algorithmes comme des fonctions sans déclarer une classe de stratégie séparée. Dans Android, le pattern est utilisé dans ViewModel et Use Cases pour isoler les algorithmes de chargement de données, de mise en cache et de gestion des erreurs. Les projets Android avec Clean Architecture utilisent Strategy pour injecter différentes implémentations de dépôt selon des drapeaux (mock, real, cache).
// 1. Interface Strategy
interface PaymentStrategy {
suspend fun pay(amount: BigDecimal): PaymentResult
}
// 2. Concrete Strategies
class CardPaymentStrategy(
private val cardNumber: String,
private val cvv: String
) : PaymentStrategy {
override suspend fun pay(amount: BigDecimal): PaymentResult {
// API bancaire via Retrofit
return PaymentResult(success = true, transactionId = "tx_${UUID.randomUUID()}")
}
}
class PayPalPaymentStrategy(
private val email: String
) : PaymentStrategy {
override suspend fun pay(amount: BigDecimal): PaymentResult {
// Intégration PayPal SDK
return PaymentResult(success = true, transactionId = "pp_${UUID.randomUUID()}")
}
}
// 3. Context
class PaymentProcessor(
private val strategy: PaymentStrategy
) {
fun setStrategy(strategy: PaymentStrategy): PaymentProcessor {
return PaymentProcessor(strategy)
}
suspend fun processPayment(amount: BigDecimal): PaymentResult {
return strategy.pay(amount)
}
}
// Utilisation dans ViewModel
class CheckoutViewModel : ViewModel() {
private var processor = PaymentProcessor(CardPaymentStrategy("4111...", "123"))
fun payWithCard() {
viewModelScope.launch {
val result = processor.processPayment(BigDecimal("99.99"))
// Traitement du résultat
}
}
}
Strategy avec Hilt/Dagger — dans les projets Android, les stratégies sont souvent injectées via DI. Hilt fournit une implémentation concrète de PaymentStrategy via @Binds ou @Provides. Cela permet de changer la stratégie sans modifier le code du contexte — il suffit de changer le module DI pour une autre compilation (debug/release). Par exemple, MockPaymentStrategy est injecté pour le débogage, une stratégie bancaire réelle pour la production. La combinaison Strategy + DI offre une flexibilité maximale.
Strategy vs State — structurellement les patterns sont identiques : les deux utilisent la composition avec une interface et des classes concrètes. La différence réside dans l'objectif : Strategy sélectionne un algorithme indépendant, State contrôle le comportement de l'objet en fonction de son état. Dans State, le contexte lui-même change la stratégie lorsque l'état change ; dans Strategy, le contexte ne contrôle pas le changement — le client définit explicitement l'algorithme. Les stratégies ne se connaissent pas entre elles, tandis que les états peuvent transitionner entre eux.
Strategy vs Command — Command encapsule une seule action comme objet, Strategy encapsule un ensemble d'algorithmes interchangeables. Command est « quoi faire » (un seul appel execute), Strategy est « comment faire » (un algorithme de plusieurs étapes). Command est utilisé pour les files d'attente, l'exécution différée, l'annulation/répétition. Strategy est utilisé pour choisir comment effectuer une tâche à l'exécution. Les commandes peuvent être paramétrées avec des stratégies, combinant les deux patterns.
| Caractéristique | Strategy | State | Command | Template Method |
|---|---|---|---|---|
| Objectif | Algorithmes interchangeables | Comportement selon l'état | Encapsulation de requête | Squelette d'algorithme |
| Changement | Explicitement par le client | Automatiquement par le contexte | Par le client ou file d'attente | Par héritage |
| Niveau | Objet (composition) | Objet (composition) | Objet | Classe (héritage) |
Strategy vs Template Method — les deux patterns définissent des algorithmes mais de manières différentes. Template Method utilise l'héritage : une classe de base définit le squelette de l'algorithme (méthode template), les sous-classes redéfinissent des étapes individuelles. Strategy utilise la composition : l'algorithme est entièrement externalisé dans une classe séparée. Template Method est plus simple pour les cas avec une structure d'algorithme fixe, Strategy — lorsque les algorithmes sont complètement différents et peuvent changer dynamiquement.
Traitement des paiements — l'exemple classique de Strategy. Le panier d'une boutique en ligne contient une liste d'articles, et le mode de paiement est choisi par l'utilisateur. Chaque méthode (carte, PayPal, Apple Pay, Google Pay, crypto-monnaie) est une stratégie séparée avec une signature pay(amount) commune. Le contexte PaymentProcessor ne sait pas comment exactement le paiement est traité — il appelle la méthode commune. L'ajout d'un nouveau mode de paiement ne nécessite pas de modifier le code du panier.
Validation des données — Strategy est utilisé pour différentes règles de validation du même champ. EmailValidatorStrategy, PhoneValidatorStrategy, AgeValidatorStrategy implémentent une interface commune ValidationStrategy avec la méthode validate(input). Un formulaire d'inscription utilise un ensemble de stratégies pour vérifier chaque champ. Les stratégies de validation peuvent être combinées dans une chaîne (Chain of Responsibility) ou appliquées toutes à la fois dans une boucle. Cela remplace de longues vérifications if-else par une collection de validateurs polymorphes.
// Stratégie de tri
protocol SortingStrategy {
func sort<T>(_ items: [T]) -> [T] where T: Comparable
}
struct QuickSortStrategy: SortingStrategy {
func sort<T>(_ items: [T]) -> [T] { /* quicksort */ items }
}
struct MergeSortStrategy: SortingStrategy {
func sort<T>(_ items: [T]) -> [T] { /* mergesort */ items }
}
class SortedDataSource<T> {
private var strategy: SortingStrategy
func display(_ items: [T]) { let sorted = strategy.sort(items) }
}
Authentification — dans les applications mobiles, les stratégies d'authentification sont changées selon le fournisseur. AuthStrategy avec les méthodes login(), logout(), getToken() est implémenté pour EmailPasswordAuth, GoogleAuth, AppleAuth, BiometricAuth. Le contexte AuthManager accepte la stratégie via DI ou fabrique. Cela permet d'ajouter de nouveaux fournisseurs d'authentification sans modifier l'écran de connexion. Le pattern Strategy est le fondement de nombreuses bibliothèques OAuth et de Firebase Authentication.
Foire aux questions
Strategy est justifié lorsque vous avez 3+ algorithmes qui peuvent changer ou s'étendre. S'il y a 2 algorithmes et qu'ils sont stables — un simple if-else est moins coûteux. Utilisez Strategy lorsque les algorithmes sont utilisés dans différentes parties de l'application, lorsque vous devez échanger des algorithmes à l'exécution, ou lorsque chaque algorithme nécessite ses propres dépendances et tests.
Non, ce sont des patterns différents avec une structure similaire. Strategy — le client sélectionne explicitement un algorithme, et les stratégies sont indépendantes. State — l'objet lui-même change son comportement lorsque son état interne change, et les états peuvent transitionner entre eux. Dans State, le contexte gère les changements d'état ; dans Strategy, c'est le code client qui le fait.
Oui, en Swift et Kotlin, une stratégie peut être passée comme closure ou lambda. Swift : typealias PaymentHandler = (Decimal) async throws -> PaymentResult. Kotlin : typealias PaymentFun = suspend (BigDecimal) -> PaymentResult. Cela simplifie le code pour les cas simples mais perd la dénomination et la documentation. Pour 1–2 algorithmes, une closure suffit ; pour 4+, il est préférable d'utiliser des classes séparées.
Chaque stratégie est testée avec un test unitaire séparé utilisant des dépendances mock. Le Contexte est testé avec une stratégie mock — vérifiant que le contexte appelle la méthode de la stratégie et passe les bons paramètres. En Swift, utilisez XCTest + protocoles pour les mocks ; en Kotlin, utilisez MockK ou Mockito. Le principal avantage : chaque stratégie est testée isolément sans configuration complexe.
Oui, Strategy est l'un des 23 patterns décrits dans le livre « Design Patterns: Elements of Reusable Object-Oriented Software » (Gamma, Helm, Johnson, Vlissides, 1994). Il appartient au groupe des patterns comportementaux. Alias : Policy. Le code d'exemple original en Smalltalk-80 est disponible dans l'édition originale du GoF.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi