Strategy (Strategie) — een gedragsmatig ontwerppatroon dat een familie van onderling vervangbare algoritmen definieert en elk ervan in een aparte klasse (Strategy) plaatst. Het patroon maakt het mogelijk om algoritmen ter plekke te kiezen: de clientcode werkt via een gemeenschappelijke Strategy-interface en de concrete implementatie wordt tijdens runtime ingevoegd. In iOS wordt het patroon geïmplementeerd via Protocol + strategieklassen, in Android — via Interface + implementaties. Strategy — een van de 23 GoF-patronen, veel gebruikt voor betalingsverwerking, validatie, sortering en filtering van gegevens. Meer — in de originele GoF-beschrijving.
Belangrijk
Strategy — een van de 23 GoF-patronen (Gang of Four), beschreven in het boek «Design Patterns: Elements of Reusable Object-Oriented Software» (1994). Het patroon lost het probleem van algoritmekeuze tijdens runtime op. In plaats van één klasse met veel voorwaardelijke operatoren (if-else, switch) te schrijven, stelt Strategy voor om elk algoritme in een aparte klasse met een gemeenschappelijke interface te plaatsen. De context (de klasse die de strategie gebruikt) bewaart een verwijzing naar de Strategy-interface en delegeert de uitvoering aan de concrete strategie.
De structuur van het patroon omvat drie elementen: Context (de context) bevat een verwijzing naar Strategy en roept de methode ervan aan; Strategy (de interface) declareert een gemeenschappelijke methode voor alle algoritmen; ConcreteStrategy (de concrete strategie) implementeert de interface en bevat het concrete algoritme. De client maakt de gewenste strategie aan en geeft deze door aan de context via de constructor, setter of methodeparameter. De context weet niet welke strategie wordt uitgevoerd — hij werkt alleen met de interface.
| Component | Rol | Voorbeeld |
|---|---|---|
| Context | Bevat een verwijzing naar Strategy | PaymentProcessor, Sorter |
| Strategy | Gemeenschappelijke interface voor algoritmen | Protocol PaymentStrategy |
| ConcreteStrategy | Concrete implementatie van het algoritme | CardPayment, PayPalPayment |
Open/Closed Principle — het belangrijkste voordeel van Strategy. Het systeem is open voor uitbreiding (er kan een nieuwe strategie worden toegevoegd) en gesloten voor wijziging (de code van de context hoeft niet te worden gewijzigd). Zonder het patroon vereist het toevoegen van een nieuw algoritme wijziging van de bestaande klasse, wat het OCP schendt en het risico op regressiefouten vergroot. Strategy verkleint ook de omvang van klassen: in plaats van een 200-regelige klasse met switch-case krijg je 6 klassen van elk 20 regels.
Strategy in Swift wordt geïmplementeerd via Protocol (de strategie-interface) en klassen of structuur-strategieën. Swift-protocollen ondersteunen associated types en generic constraints, wat flexibiliteit biedt bij het ontwerpen van strategieën. De context is meestal een ViewModel-klasse of een service die de strategie ontvangt in init of via een eigenschap. Het patroon wordt veel gebruikt in iOS-projecten voor gebeurtenisafhandeling, animaties, gegevensopmaak en UI-strategieën.
// 1. Protocol Strategy
protocol PaymentStrategy {
func pay(amount: Decimal) async throws -> PaymentResult
}
// 2. Concrete strategieën
struct CardPaymentStrategy: PaymentStrategy {
let cardNumber: String
let cvv: String
func pay(amount: Decimal) async throws -> PaymentResult {
// Verzenden van verzoek naar de bank-API
return PaymentResult(status: .success, transactionId: "tx_\(UUID())")
}
}
struct PayPalPaymentStrategy: PaymentStrategy {
let email: String
func pay(amount: Decimal) async throws -> PaymentResult {
// Doorverwijzing naar 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)
}
}
// Gebruik
let processor = PaymentProcessor(strategy: CardPaymentStrategy(cardNumber: "4111...", cvv: "123"))
let result = try await processor.processPayment(amount: 99.99)
Strategy in SwiftUI — het patroon integreert op natuurlijke wijze met MVVM. De ViewModel bevat een strategie-eigenschap en roept de methode ervan aan bij een gebruikersactie. SwiftUI View ontvangt gegevens via @Published of @State — de strategie verbergt implementatiedetails voor de View. Bijvoorbeeld, de tekstvalidatiestrategie (emailValidator, phoneValidator) wordt gewijzigd afhankelijk van het type invoerveld. Het combineren van Strategy met SwiftUI biedt flexibiliteit zonder overerving van UIKit.
Strategy in Kotlin gebruikt Interface op taalniveau en functionele interfaces (SAM) voor vereenvoudiging. Kotlin ondersteunt lambda's, wat het mogelijk maakt om algoritmen als functies door te geven zonder een aparte strategieklasse te declareren. In Android wordt het patroon toegepast in ViewModel en Use Cases voor het isoleren van algoritmen voor gegevens laden, caching en foutafhandeling. Android-projecten met Clean Architecture gebruiken Strategy voor het injecteren van verschillende repository-implementaties op basis van vlaggen (mock, real, cache).
// 1. Interface Strategy
interface PaymentStrategy {
suspend fun pay(amount: BigDecimal): PaymentResult
}
// 2. Concrete strategieën
class CardPaymentStrategy(
private val cardNumber: String,
private val cvv: String
) : PaymentStrategy {
override suspend fun pay(amount: BigDecimal): PaymentResult {
// Bank-API via Retrofit
return PaymentResult(success = true, transactionId = "tx_${UUID.randomUUID()}")
}
}
class PayPalPaymentStrategy(
private val email: String
) : PaymentStrategy {
override suspend fun pay(amount: BigDecimal): PaymentResult {
// PayPal SDK integration
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)
}
}
// Gebruik in ViewModel
class CheckoutViewModel : ViewModel() {
private var processor = PaymentProcessor(CardPaymentStrategy("4111...", "123"))
fun payWithCard() {
viewModelScope.launch {
val result = processor.processPayment(BigDecimal("99.99"))
// Verwerking van resultaat
}
}
}
Strategy met Hilt/Dagger — in Android-projecten worden strategieën vaak geïnjecteerd via DI. Hilt levert de concrete implementatie van PaymentStrategy via @Binds of @Provides. Dit maakt het mogelijk om de strategie te wijzigen zonder de contextcode aan te passen — het volstaat om de DI-module te wijzigen voor een andere build (debug/release). Bijvoorbeeld, voor debugging wordt MockPaymentStrategy geïnjecteerd, voor productie — de echte bankstrategie. De combinatie Strategy + DI biedt maximale flexibiliteit.
Strategy vs State — structureel zijn de patronen identiek: beide gebruiken compositie met een interface en concrete klassen. Het verschil zit in het doel: Strategy selecteert een onafhankelijk algoritme, State beheert het gedrag van het object op basis van zijn toestand. In State verandert de context zelf de strategie bij toestandswijziging, in Strategy controleert de context het omschakelen niet — de client specificeert expliciet het algoritme. Strategieën weten niets van elkaar, toestanden kunnen in elkaar overgaan.
Strategy vs Command — Command kapselt een enkele actie in als object, Strategy kapselt een set onderling vervangbare algoritmen in. Command — «wat te doen» (één execute-aanroep), Strategy — «hoe te doen» (algoritme van meerdere stappen). Command wordt gebruikt voor wachtrijen, uitgestelde uitvoering, undo/redo. Strategy — voor het kiezen van de uitvoeringswijze van een taak tijdens runtime. Commando's kunnen worden geparametriseerd met strategieën, waarbij beide patronen worden gecombineerd.
| Kenmerk | Strategy | State | Command | Template Method |
|---|---|---|---|---|
| Doel | Onderling vervangbare algoritmen | Gedrag afhankelijk van toestand | Inkapseling van verzoek | Skelet van algoritme |
| Wijziging | Expliciet door client | Automatisch door context | Door client of wachtrij | Door overerving |
| Niveau | Object (compositie) | Object (compositie) | Object | Klasse (overerving) |
Strategy vs Template Method — beide patronen definiëren algoritmen, maar op verschillende manieren. Template Method gebruikt overerving: de basisklasse definieert het skelet van het algoritme (de sjabloonmethode), subklassen overschrijven individuele stappen. Strategy gebruikt compositie: het algoritme is volledig ondergebracht in een aparte klasse. Template Method is eenvoudiger voor gevallen met een vaste algoritmestructuur, Strategy — wanneer algoritmen volledig verschillend zijn en dynamisch kunnen veranderen.
Betalingsverwerking — het klassieke voorbeeld van Strategy. Het winkelmandje van een webwinkel bevat een lijst producten en de betaalmethode wordt door de gebruiker gekozen. Elke methode (kaart, PayPal, Apple Pay, Google Pay, cryptovaluta) — een aparte strategie met de gemeenschappelijke handtekening pay(amount). De context PaymentProcessor weet niet hoe de betaling precies wordt uitgevoerd — hij roept de gemeenschappelijke methode aan. Het toevoegen van een nieuwe betaalmethode vereist geen wijziging van de winkelwagencode.
Gegevensvalidatie — Strategy wordt toegepast voor verschillende validatieregels van hetzelfde veld. EmailValidatorStrategy, PhoneValidatorStrategy, AgeValidatorStrategy implementeren de gemeenschappelijke interface ValidationStrategy met de methode validate(input). Het registratieformulier gebruikt een set strategieën om elk veld te controleren. Validatiestrategieën kunnen in een keten worden gecombineerd (Chain of Responsibility) of allemaal tegelijk in een lus worden toegepast. Dit vervangt lange if-else-controles door een verzameling polymorfe validators.
// Sorteerstrategie
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) }
}
Authenticatie — in mobiele applicaties schakelen authenticatiestrategieën afhankelijk van de provider. AuthStrategy met methoden login(), logout(), getToken() wordt geïmplementeerd voor EmailPasswordAuth, GoogleAuth, AppleAuth, BiometricAuth. De context AuthManager ontvangt de strategie via DI of een fabriek. Dit maakt het mogelijk om nieuwe authenticatieproviders toe te voegen zonder het inlogscherm te wijzigen. Het Strategy-patroon — de basis voor veel OAuth-bibliotheken en Firebase Authentication.
Veelgestelde vragen
Strategy is gerechtvaardigd wanneer u 3+ algoritmen heeft die kunnen veranderen of worden uitgebreid. Als er 2 algoritmen zijn en ze stabiel zijn — is een simpele if-else minder kostbaar. Gebruik Strategy wanneer algoritmen in verschillende delen van de applicatie worden gebruikt, wanneer algoritmen tijdens runtime moeten worden vervangen of wanneer elk algoritme zijn eigen afhankelijkheden en tests vereist.
Nee, dit zijn verschillende patronen met een vergelijkbare structuur. Strategy — de client kiest expliciet het algoritme en strategieën zijn onafhankelijk. State — het object verandert zelf zijn gedrag bij wijziging van de interne toestand en toestanden kunnen in elkaar overgaan. In State beheert de context de toestandswijziging, in Strategy — de clientcode.
Ja, in Swift en Kotlin kan de strategie worden doorgegeven als een closure of lambda. Swift: typealias PaymentHandler = (Decimal) async throws -> PaymentResult. Kotlin: typealias PaymentFun = suspend (BigDecimal) -> PaymentResult. Dit vereenvoudigt de code voor eenvoudige gevallen, maar naamgeving en documentatie gaan verloren. Voor 1-2 algoritmen — is closure voldoende, voor 4+ — zijn aparte klassen beter.
Elke strategie wordt getest met een aparte unittest met mock-afhankelijkheden. De context wordt getest met een mock-strategie — er wordt gecontroleerd of de context de methode van de strategie aanroept en de juiste parameters doorgeeft. In Swift gebruikt u XCTest + protocollen voor mock, in Kotlin — MockK of Mockito. Het belangrijkste voordeel: elke strategie wordt geïsoleerd getest zonder complexe configuratie.
Ja, Strategy (Strategie) — een van de 23 patronen beschreven in het boek «Design Patterns: Elements of Reusable Object-Oriented Software» (Gamma, Helm, Johnson, Vlissides, 1994). Het behoort tot de groep gedragspatronen (Behavioral Patterns). Synoniemen: Policy (Beleid). De originele voorbeeldcode in Smalltalk-80 is beschikbaar in de originele uitgave van GoF.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook