Strategy (Strategie) ist ein Verhaltensentwurfsmuster, das eine Familie austauschbarer Algorithmen definiert und jeden von ihnen in eine separate Klasse (Strategy) einordnet. Das Muster ermöglicht die Auswahl eines Algorithmus im laufenden Betrieb: Der Client-Code arbeitet über eine gemeinsame Strategy-Schnittstelle, und die konkrete Implementierung wird zur Laufzeit eingesetzt. Unter iOS wird das Muster über Protocol + Strategieklassen implementiert, unter Android über Interface + Implementierungen. Strategy ist eines der 23 GoF-Muster und wird häufig für Zahlungsabwicklung, Validierung, Sortierung und Datenfilterung verwendet. Weitere Informationen finden Sie in der originalen GoF-Beschreibung.
Wichtige Punkte
Strategy ist eines der 23 GoF-Muster (Gang of Four), beschrieben im Buch „Design Patterns: Elements of Reusable Object-Oriented Software" (1994). Das Muster löst das Problem der Auswahl eines Algorithmus zur Laufzeit. Anstatt eine einzelne Klasse mit vielen bedingten Anweisungen (if-else, switch) zu schreiben, schlägt Strategy vor, jeden Algorithmus in eine separate Klasse mit einer gemeinsamen Schnittstelle zu extrahieren. Der Kontext (die Klasse, die die Strategie verwendet) hält eine Referenz auf die Strategy-Schnittstelle und delegiert die Ausführung an die konkrete Strategie.
Die Struktur des Musters umfasst drei Elemente: Context (Kontext) hält eine Referenz auf Strategy und ruft dessen Methode auf; Strategy (Schnittstelle) deklariert eine gemeinsame Methode für alle Algorithmen; ConcreteStrategy (konkrete Strategie) implementiert die Schnittstelle und enthält den konkreten Algorithmus. Der Client erstellt die gewünschte Strategie und übergibt sie an den Kontext über einen Konstruktor, Setter oder Methodenparameter. Der Kontext weiß nicht, welche spezifische Strategie ausgeführt wird — er arbeitet nur mit der Schnittstelle.
| Komponente | Rolle | Beispiel |
|---|---|---|
| Context | Hält eine Referenz auf Strategy | PaymentProcessor, Sorter |
| Strategy | Gemeinsame Schnittstelle für Algorithmen | Protocol PaymentStrategy |
| ConcreteStrategy | Konkrete Implementierung des Algorithmus | CardPayment, PayPalPayment |
Open/Closed-Prinzip — der Hauptvorteil von Strategy. Das System ist offen für Erweiterung (neue Strategie kann hinzugefügt werden) und geschlossen für Änderung (Kontextcode muss nicht geändert werden). Ohne das Muster erfordert das Hinzufügen eines neuen Algorithmus die Änderung der vorhandenen Klasse, was OCP verletzt und das Risiko von Regressionen erhöht. Strategy reduziert auch die Klassengröße: Statt einer 200-zeiligen Klasse mit switch-case erhalten Sie 6 Klassen mit je 20 Zeilen.
Strategy in Swift wird über Protocol (Strategieschnittstelle) und Strategieklassen oder -strukturen implementiert. Swift-Protokolle unterstützen assoziierte Typen und generische Einschränkungen, was Flexibilität beim Entwerfen von Strategien bietet. Der Kontext ist normalerweise eine ViewModel-Klasse oder ein Dienst, der die Strategie im init oder über eine Eigenschaft akzeptiert. Das Muster wird häufig in iOS-Projekten für Ereignisbehandlung, Animationen, Datenformatierung und UI-Strategien verwendet.
// 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 {
// Anfrage an Bank-API senden
return PaymentResult(status: .success, transactionId: "tx_\(UUID())")
}
}
struct PayPalPaymentStrategy: PaymentStrategy {
let email: String
func pay(amount: Decimal) async throws -> PaymentResult {
// Weiterleitung an 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)
}
}
// Verwendung
let processor = PaymentProcessor(strategy: CardPaymentStrategy(cardNumber: "4111...", cvv: "123"))
let result = try await processor.processPayment(amount: 99.99)
Strategy in SwiftUI — das Muster integriert sich natürlich mit MVVM. Ein ViewModel enthält eine Strategieeigenschaft und ruft deren Methode bei Benutzeraktion auf. Die SwiftUI-View empfängt Daten über @Published oder @State — die Strategie verbirgt Implementierungsdetails vor der View. Beispielsweise wird eine Textvalidierungsstrategie (emailValidator, phoneValidator) je nach Eingabefeldtyp ausgetauscht. Die Kombination von Strategy mit SwiftUI bietet Flexibilität ohne Vererbung von UIKit.
Strategy in Kotlin verwendet Interface auf Sprachebene und funktionale Schnittstellen (SAM) zur Vereinfachung. Kotlin unterstützt Lambdas, was das Übergeben von Algorithmen als Funktionen ermöglicht, ohne eine separate Strategieklasse deklarieren zu müssen. In Android wird das Muster in ViewModel und Use Cases verwendet, um Algorithmen für Datenladen, Caching und Fehlerbehandlung zu isolieren. Android-Projekte mit Clean Architecture verwenden Strategy, um verschiedene Repository-Implementierungen basierend auf Flags (mock, real, cache) zu injizieren.
// 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 {
// Bank-API über 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)
}
}
// Verwendung im ViewModel
class CheckoutViewModel : ViewModel() {
private var processor = PaymentProcessor(CardPaymentStrategy("4111...", "123"))
fun payWithCard() {
viewModelScope.launch {
val result = processor.processPayment(BigDecimal("99.99"))
// Ergebnisverarbeitung
}
}
}
Strategy mit Hilt/Dagger — in Android-Projekten werden Strategien oft über DI injiziert. Hilt bietet eine konkrete Implementierung von PaymentStrategy über @Binds oder @Provides. Dies ermöglicht das Ändern der Strategie ohne Änderung des Kontextcodes — einfach das DI-Modul für einen anderen Build (debug/release) ändern. Beispielsweise wird MockPaymentStrategy zum Debuggen injiziert, eine echte Bankstrategie für die Produktion. Die Kombination von Strategy + DI bietet maximale Flexibilität.
Strategy vs State — strukturell sind die Muster identisch: beide verwenden Komposition mit einer Schnittstelle und konkreten Klassen. Der Unterschied liegt im Zweck: Strategy wählt einen unabhängigen Algorithmus aus, State steuert das Objektverhalten abhängig von seinem Zustand. In State ändert der Kontext selbst die Strategie, wenn sich der Zustand ändert; in Strategy steuert der Kontext das Umschalten nicht — der Client legt den Algorithmus explizit fest. Strategien wissen nichts voneinander, während Zustände ineinander übergehen können.
Strategy vs Command — Command kapselt eine einzelne Aktion als Objekt, Strategy kapselt eine Reihe austauschbarer Algorithmen. Command ist „was zu tun ist" (ein einzelner execute-Aufruf), Strategy ist „wie es zu tun ist" (ein Algorithmus aus mehreren Schritten). Command wird für Warteschlangen, verzögerte Ausführung, Rückgängig/Wiederholen verwendet. Strategy wird verwendet, um zur Laufzeit auszuwählen, wie eine Aufgabe ausgeführt wird. Befehle können mit Strategien parametrisiert werden, wodurch beide Muster kombiniert werden.
| Eigenschaft | Strategy | State | Command | Template Method |
|---|---|---|---|---|
| Zweck | Austauschbare Algorithmen | Verhalten nach Zustand | Kapselung von Anfragen | Algorithmusgerüst |
| Wechsel | Explizit durch Client | Automatisch durch Kontext | Durch Client oder Warteschlange | Durch Vererbung |
| Ebene | Objekt (Komposition) | Objekt (Komposition) | Objekt | Klasse (Vererbung) |
Strategy vs Template Method — beide Muster definieren Algorithmen, aber auf unterschiedliche Weise. Template Method verwendet Vererbung: eine Basisklasse definiert das Algorithmusgerüst (Template-Methode), Unterklassen überschreiben einzelne Schritte. Strategy verwendet Komposition: Der Algorithmus ist vollständig in eine separate Klasse ausgelagert. Template Method ist einfacher für Fälle mit fester Algorithmusstruktur, Strategy — wenn Algorithmen völlig unterschiedlich sind und sich dynamisch ändern können.
Zahlungsabwicklung — das klassische Strategy-Beispiel. Der Warenkorb eines Online-Shops enthält eine Liste von Artikeln, und die Zahlungsmethode wird vom Benutzer gewählt. Jede Methode (Karte, PayPal, Apple Pay, Google Pay, Kryptowährung) ist eine separate Strategie mit einer gemeinsamen pay(amount)-Signatur. Der PaymentProcessor-Kontext weiß nicht, wie die Zahlung genau abgewickelt wird — er ruft die gemeinsame Methode auf. Das Hinzufügen einer neuen Zahlungsmethode erfordert keine Änderung des Warenkorbcodes.
Datenvalidierung — Strategy wird für unterschiedliche Validierungsregeln desselben Feldes verwendet. EmailValidatorStrategy, PhoneValidatorStrategy, AgeValidatorStrategy implementieren eine gemeinsame ValidationStrategy-Schnittstelle mit der Methode validate(input). Ein Registrierungsformular verwendet eine Reihe von Strategien, um jedes Feld zu prüfen. Validierungsstrategien können in einer Kette (Chain of Responsibility) kombiniert oder in einer Schleife alle auf einmal angewendet werden. Dies ersetzt lange if-else-Prüfungen durch eine Sammlung polymorpher Validatoren.
// Sortierstrategie
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) }
}
Authentifizierung — in mobilen Apps werden Authentifizierungsstrategien je nach Anbieter umgeschaltet. AuthStrategy mit den Methoden login(), logout(), getToken() wird für EmailPasswordAuth, GoogleAuth, AppleAuth, BiometricAuth implementiert. Der AuthManager-Kontext akzeptiert die Strategie über DI oder Factory. Dies ermöglicht das Hinzufügen neuer Authentifizierungsanbieter ohne Änderung des Login-Bildschirms. Das Strategy-Pattern ist die Grundlage für viele OAuth-Bibliotheken und Firebase Authentication.
Häufig gestellte Fragen
Strategy ist gerechtfertigt, wenn Sie 3+ Algorithmen haben, die sich ändern oder erweitern können. Wenn es 2 Algorithmen gibt und diese stabil sind, ist ein einfaches if-else weniger aufwendig. Verwenden Sie Strategy, wenn Algorithmen in verschiedenen Teilen der Anwendung verwendet werden, wenn Algorithmen zur Laufzeit ausgetauscht werden müssen oder wenn jeder Algorithmus eigene Abhängigkeiten und Tests erfordert.
Nein, es sind unterschiedliche Muster mit ähnlicher Struktur. Strategy — der Client wählt explizit einen Algorithmus aus, und die Strategien sind unabhängig. State — das Objekt selbst ändert sein Verhalten, wenn sich sein interner Zustand ändert, und Zustände können ineinander übergehen. In State verwaltet der Kontext die Zustandsänderungen; in Strategy tut dies der Client-Code.
Ja, in Swift und Kotlin kann eine Strategie als Closure oder Lambda übergeben werden. Swift: typealias PaymentHandler = (Decimal) async throws -> PaymentResult. Kotlin: typealias PaymentFun = suspend (BigDecimal) -> PaymentResult. Dies vereinfacht den Code für einfache Fälle, verliert aber Benennung und Dokumentation. Für 1–2 Algorithmen reicht ein Closure; für 4+ sind separate Klassen besser.
Jede Strategie wird mit einem separaten Unit-Test unter Verwendung von Mock-Abhängigkeiten getestet. Der Kontext wird mit einer Mock-Strategie getestet — es wird überprüft, ob der Kontext die Methode der Strategie aufruft und die richtigen Parameter übergibt. In Swift verwenden Sie XCTest + Protokolle für Mocks; in Kotlin verwenden Sie MockK oder Mockito. Der Hauptvorteil: Jede Strategie wird isoliert ohne komplexe Einrichtung getestet.
Ja, Strategy ist eines der 23 Muster, die im Buch „Design Patterns: Elements of Reusable Object-Oriented Software" (Gamma, Helm, Johnson, Vlissides, 1994) beschrieben sind. Es gehört zur Gruppe der Verhaltensmuster. Aliase: Policy. Der ursprüngliche Smalltalk-80-Beispielcode ist in der Originalausgabe von GoF verfügbar.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch