Strategy — co to jest, wzorzec strategii w iOS i Android

Autor: IT Sectr Opublikowano: 2026-02-18 Czas czytania: 9 min

Strategy (Strategia) — behawioralny wzorzec projektowania, który definiuje rodzinę wymiennych algorytmów i umieszcza każdy z nich w osobnej klasie (Strategy). Wzorzec pozwala wybrać algorytm w locie: kod kliencki działa przez wspólny interfejs Strategy, a konkretna implementacja jest podstawiana w czasie wykonania. W iOS wzorzec implementuje się przez Protocol + klasy-strategie, w Android — przez Interface + implementacje. Strategy — jeden z 23 wzorców GoF, szeroko stosowany do przetwarzania płatności, walidacji, sortowania i filtrowania danych. Więcej — w oryginalnym opisie GoF.

Najważniejsze

  • Strategy — behawioralny wzorzec GoF dla rodziny wymiennych algorytmów
  • Inkapsulacja algorytmów — każdy algorytm jest izolowany we własnej klasie
  • Interfejs Strategy — wspólny kontrakt implementowany przez wszystkie konkretne strategie
  • Kompozycja zamiast dziedziczenia — kontekst przechowuje referencję do strategii, a nie dziedziczy zachowanie
  • Open/Closed Principle — nowe strategie są dodawane bez zmiany istniejącego kodu

Czym jest wzorzec Strategy: istota i struktura

Strategy — jeden z 23 wzorców GoF (Gang of Four), opisany w książce «Design Patterns: Elements of Reusable Object-Oriented Software» (1994). Wzorzec rozwiązuje problem wyboru algorytmu w czasie wykonania. Zamiast pisać jedną klasę z wieloma operatorami warunkowymi (if-else, switch), Strategy proponuje wydzielenie każdego algorytmu do osobnej klasy ze wspólnym interfejsem. Kontekst (klasa używająca strategii) przechowuje referencję do interfejsu Strategy i deleguje wykonanie do konkretnej strategii.

Struktura wzorca obejmuje trzy elementy: Context (kontekst) zawiera referencję do Strategy i wywołuje jej metodę; Strategy (interfejs) deklaruje wspólną metodę dla wszystkich algorytmów; ConcreteStrategy (konkretna strategia) implementuje interfejs i zawiera konkretny algorytm. Klient tworzy potrzebną strategię i przekazuje ją do kontekstu przez konstruktor, setter lub parametr metody. Kontekst nie wie, która strategia jest wykonywana — działa tylko z interfejsem.

KomponentRolaPrzykład
ContextZawiera referencję do StrategyPaymentProcessor, Sorter
StrategyWspólny interfejs dla algorytmówProtocol PaymentStrategy
ConcreteStrategyKonkretna implementacja algorytmuCardPayment, PayPalPayment

Zasada Open/Closed — główna zaleta Strategy. System jest otwarty na rozszerzanie (można dodać nową strategię) i zamknięty na modyfikacje (nie trzeba zmieniać kodu kontekstu). Bez wzorca dodanie nowego algorytmu wymaga zmiany istniejącej klasy, co narusza OCP i zwiększa ryzyko błędów regresyjnych. Strategy zmniejsza również rozmiar klas: zamiast 200-wierszowej klasy z switch-case otrzymujemy 6 klas po 20 wierszy każda.

Strategy w iOS: implementacja w Swift z Protocol

Strategy w Swift implementuje się przez Protocol (interfejs strategii) i klasy lub struktury-strategie. Protokoły Swift obsługują associated types i generic constraints, co daje elastyczność przy projektowaniu strategii. Kontekstem jest zazwyczaj klasa ViewModel lub serwis, który przyjmuje strategię w init lub przez właściwość. Wzorzec jest szeroko stosowany w projektach iOS do obsługi zdarzeń, animacji, formatowania danych i strategii UI.

swift
// 1. Protokół Strategy
protocol PaymentStrategy {
    func pay(amount: Decimal) async throws -> PaymentResult
}

// 2. Konkretne strategie
struct CardPaymentStrategy: PaymentStrategy {
    let cardNumber: String
    let cvv: String

    func pay(amount: Decimal) async throws -> PaymentResult {
        // Wysłanie zapytania do API banku
        return PaymentResult(status: .success, transactionId: "tx_\(UUID())")
    }
}

struct PayPalPaymentStrategy: PaymentStrategy {
    let email: String

    func pay(amount: Decimal) async throws -> PaymentResult {
        // Przekierowanie do PayPal SDK
        return PaymentResult(status: .success, transactionId: "pp_\(UUID())")
    }
}

// 3. Kontekst
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)
    }
}

// Użycie
let processor = PaymentProcessor(strategy: CardPaymentStrategy(cardNumber: "4111...", cvv: "123"))
let result = try await processor.processPayment(amount: 99.99)

Strategy w SwiftUI — wzorzec naturalnie integruje się z MVVM. ViewModel zawiera właściwość strategii i wywołuje jej metodę przy działaniu użytkownika. SwiftUI View otrzymuje dane przez @Published lub @State — strategia ukrywa szczegóły implementacji przed View. Na przykład strategia walidacji tekstu (emailValidator, phoneValidator) jest podmieniana w zależności od typu pola wejściowego. Połączenie Strategy z SwiftUI daje elastyczność bez dziedziczenia z UIKit.

Strategy w Android: implementacja w Kotlin z Interface

Strategy w Kotlin używa Interface na poziomie języka i funkcjonalnych interfejsów (SAM) dla uproszczenia. Kotlin obsługuje lambdy, co pozwala przekazywać algorytmy jako funkcje bez deklarowania osobnej klasy strategii. W Android wzorzec stosuje się w ViewModel i Use Cases do izolacji algorytmów ładowania danych, buforowania i obsługi błędów. Projekty Android z Clean Architecture używają Strategy do wstrzykiwania różnych implementacji repozytorium w zależności od flag (mock, real, cache).

kotlin
// 1. Interfejs Strategy
interface PaymentStrategy {
    suspend fun pay(amount: BigDecimal): PaymentResult
}

// 2. Konkretne strategie
class CardPaymentStrategy(
    private val cardNumber: String,
    private val cvv: String
) : PaymentStrategy {
    override suspend fun pay(amount: BigDecimal): PaymentResult {
        // API bankowe przez 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. Kontekst
class PaymentProcessor(
    private val strategy: PaymentStrategy
) {
    fun setStrategy(strategy: PaymentStrategy): PaymentProcessor {
        return PaymentProcessor(strategy)
    }

    suspend fun processPayment(amount: BigDecimal): PaymentResult {
        return strategy.pay(amount)
    }
}

// Użycie w ViewModel
class CheckoutViewModel : ViewModel() {
    private var processor = PaymentProcessor(CardPaymentStrategy("4111...", "123"))

    fun payWithCard() {
        viewModelScope.launch {
            val result = processor.processPayment(BigDecimal("99.99"))
            // Przetwarzanie wyniku
        }
    }
}

Strategy z Hilt/Dagger — w projektach Android strategie są często wstrzykiwane przez DI. Hilt dostarcza konkretną implementację PaymentStrategy przez @Binds lub @Provides. Pozwala to zmieniać strategię bez zmiany kodu kontekstu — wystarczy zmienić moduł DI dla innej kompilacji (debug/release). Na przykład do debugowania wstrzykiwana jest MockPaymentStrategy, a do produkcji — rzeczywista strategia bankowa. Połączenie Strategy + DI daje maksymalną elastyczność.

Porównanie Strategy z State, Command i Template Method

Strategy vs State — strukturalnie wzorce są identyczne: oba używają kompozycji z interfejsem i konkretnymi klasami. Różnica polega na przeznaczeniu: Strategy wybiera niezależny algorytm, State zarządza zachowaniem obiektu w zależności od jego stanu. W State kontekst sam zmienia strategię przy zmianie stanu, w Strategy kontekst nie kontroluje przełączania — klient jawnie określa algorytm. Strategie nie wiedzą o sobie nawzajem, stany mogą przechodzić między sobą.

Strategy vs Command — Command enkapsuluje pojedyncze działanie jako obiekt, Strategy enkapsuluje zestaw wymiennych algorytmów. Command — «co zrobić» (jedno wywołanie execute), Strategy — «jak zrobić» (algorytm z kilku kroków). Command jest używane do kolejek, opóźnionego wykonania, undo/redo. Strategy — do wyboru sposobu wykonania zadania w czasie wykonania. Komendy można parametryzować strategiami, łącząc oba wzorce.

CechaStrategyStateCommandTemplate Method
PrzeznaczenieWymienne algorytmyZachowanie od stanuEnkapsulacja żądaniaSzkielet algorytmu
ZmianaJawnie przez klientaAutomatycznie przez kontekstPrzez klienta lub kolejkęPrzez dziedziczenie
PoziomObiektowy (kompozycja)Obiektowy (kompozycja)ObiektowyKlasowy (dziedziczenie)

Strategy vs Template Method — oba wzorce definiują algorytmy, ale różnymi sposobami. Template Method używa dziedziczenia: klasa bazowa definiuje szkielet algorytmu (metoda szablonowa), podklasy nadpisują poszczególne kroki. Strategy używa kompozycji: algorytm jest w całości wydzielony do osobnej klasy. Template Method jest prostszy dla przypadków o ustalonej strukturze algorytmu, Strategy — gdy algorytmy są całkowicie różne i mogą się dynamicznie zmieniać.

Rzeczywiste przykłady użycia wzorca Strategy

Przetwarzanie płatności — klasyczny przykład Strategy. Koszyk sklepu internetowego zawiera listę produktów, a sposób płatności wybiera użytkownik. Każdy sposób (karta, PayPal, Apple Pay, Google Pay, kryptowaluta) — osobna strategia ze wspólną sygnaturą pay(amount). Kontekst PaymentProcessor nie wie, jak dokładnie przeprowadzana jest płatność — wywołuje wspólną metodę. Dodanie nowego sposobu płatności nie wymaga zmiany kodu koszyka.

Walidacja danych — Strategy stosuje się do różnych reguł walidacji tego samego pola. EmailValidatorStrategy, PhoneValidatorStrategy, AgeValidatorStrategy implementują wspólny interfejs ValidationStrategy z metodą validate(input). Formularz rejestracji używa zestawu strategii do sprawdzania każdego pola. Strategie walidacji można łączyć w łańcuch (Chain of Responsibility) lub stosować wszystkie naraz w pętli. Zastępuje to długie sprawdzenia if-else kolekcją polimorficznych walidatorów.

swift
// Strategia sortowania
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) }
}

Uwierzytelnianie — w aplikacjach mobilnych strategie uwierzytelniania przełączają się w zależności od dostawcy. AuthStrategy z metodami login(), logout(), getToken() jest implementowana dla EmailPasswordAuth, GoogleAuth, AppleAuth, BiometricAuth. Kontekst AuthManager przyjmuje strategię przez DI lub fabrykę. Pozwala to dodawać nowych dostawców uwierzytelniania bez zmiany ekranu logowania. Wzorzec Strategy — podstawa wielu bibliotek OAuth i Firebase Authentication.

Często zadawane pytania

Kiedy używać Strategy zamiast if-else?

Strategy jest uzasadniony, gdy masz 3+ algorytmy, które mogą się zmieniać lub rozszerzać. Jeśli algorytmów jest 2 i są stabilne — prosty if-else jest mniej kosztowny. Używaj Strategy, gdy algorytmy są używane w różnych częściach aplikacji, gdy trzeba podmieniać algorytmy w czasie wykonania lub gdy każdy algorytm wymaga własnych zależności i testów.

Czy Strategy to to samo co State?

Nie, to różne wzorce o podobnej strukturze. Strategy — klient jawnie wybiera algorytm, a strategie są niezależne. State — obiekt sam zmienia swoje zachowanie przy zmianie stanu wewnętrznego, a stany mogą przechodzić między sobą. W State kontekst zarządza zmianą stanu, w Strategy — kod kliencki.

Czy można używać Strategy bez klas — przez closures?

Tak, w Swift i Kotlin strategię można przekazać jako domknięcie lub lambdę. Swift: typealias PaymentHandler = (Decimal) async throws -> PaymentResult. Kotlin: typealias PaymentFun = suspend (BigDecimal) -> PaymentResult. Upraszcza to kod dla prostych przypadków, ale traci się nazewnictwo i dokumentację. Dla 1-2 algorytmów — closure wystarczy, dla 4+ — lepiej osobne klasy.

Jak testować wzorzec Strategy?

Każda strategia jest testowana osobnym testem jednostkowym z mock-zależnościami. Kontekst testuje się z mock-strategią — sprawdza się, że kontekst wywołuje metodę strategii i przekazuje prawidłowe parametry. W Swift użyj XCTest + protokoły do mock, w Kotlin — MockK lub Mockito. Główna zaleta: każda strategia jest testowana w izolacji bez skomplikowanej konfiguracji.

Czy Strategy to wzorzec GoF?

Tak, Strategy (Strategia) — jeden z 23 wzorców opisanych w książce «Design Patterns: Elements of Reusable Object-Oriented Software» (Gamma, Helm, Johnson, Vlissides, 1994). Należy do grupy wzorców behawioralnych (Behavioral Patterns). Synonimy: Policy (Polityka). Oryginalny kod przykładu w Smalltalk-80 jest dostępny w oryginalnym wydaniu GoF.

Podsumowanie

  • Strategy — behawioralny wzorzec dla wymiennych algorytmów ze wspólnym interfejsem
  • Inkapsulacja — każdy algorytm jest izolowany w osobnej klasie-strategii
  • iOS Swift — implementacja przez Protocol, struktury i domknięcia
  • Android Kotlin — implementacja przez Interface, lambdy i DI z Hilt
  • Open/Closed Principle — nowe strategie są dodawane bez zmiany kontekstu
  • Zastosowanie — płatności, walidacja, sortowanie, uwierzytelnianie, formatowanie

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również