Strategy — what it is, the strategy pattern in iOS and Android

Author: IT Sectr Published: 2026-02-18 Reading time: 9 min

Strategy is a behavioral design pattern that defines a family of interchangeable algorithms and places each of them in a separate class (Strategy). The pattern allows selecting an algorithm on the fly: client code works through a common Strategy interface, and the specific implementation is substituted at runtime. In iOS, the pattern is implemented via Protocol + strategy classes, in Android — via Interface + implementations. Strategy is one of the 23 GoF patterns, widely used for payment processing, validation, sorting and data filtering. For more details — see the original GoF description.

Key Takeaways

  • Strategy — a GoF behavioral pattern for a family of interchangeable algorithms
  • Encapsulation of algorithms — each algorithm is isolated in its own class
  • Strategy Interface — a common contract implemented by all concrete strategies
  • Composition over inheritance — the context holds a reference to a strategy, not inheriting behavior
  • Open/Closed Principle — new strategies can be added without changing existing code

What is the Strategy Pattern: Essence and Structure

Strategy is one of the 23 GoF (Gang of Four) patterns, described in the book "Design Patterns: Elements of Reusable Object-Oriented Software" (1994). The pattern solves the problem of selecting an algorithm at runtime. Instead of writing a single class with many conditional statements (if-else, switch), Strategy proposes extracting each algorithm into a separate class with a common interface. The context (the class that uses the strategy) holds a reference to the Strategy interface and delegates execution to the concrete strategy.

The structure of the pattern includes three elements: Context holds a reference to Strategy and calls its method; Strategy (interface) declares a common method for all algorithms; ConcreteStrategy implements the interface and contains the concrete algorithm. The client creates the desired strategy and passes it to the context through a constructor, setter, or method parameter. The context does not know which specific strategy is being executed — it only works with the interface.

ComponentRoleExample
ContextHolds a reference to StrategyPaymentProcessor, Sorter
StrategyCommon interface for algorithmsProtocol PaymentStrategy
ConcreteStrategyConcrete algorithm implementationCardPayment, PayPalPayment

Open/Closed Principle — the main advantage of Strategy. The system is open for extension (new strategy can be added) and closed for modification (context code does not need to change). Without the pattern, adding a new algorithm requires changing the existing class, which violates OCP and increases the risk of regression errors. Strategy also reduces class size: instead of a 200-line class with a switch-case, you get 6 classes of 20 lines each.

Strategy in iOS: Swift Implementation with Protocol

Strategy in Swift is implemented via Protocol (strategy interface) and strategy classes or structs. Swift protocols support associated types and generic constraints, providing flexibility when designing strategies. The context is usually a ViewModel class or service that accepts the strategy in init or via a property. The pattern is widely used in iOS projects for event handling, animations, data formatting and UI strategies.

swift
// 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 {
        // Sending request to banking API
        return PaymentResult(status: .success, transactionId: "tx_\(UUID())")
    }
}

struct PayPalPaymentStrategy: PaymentStrategy {
    let email: String

    func pay(amount: Decimal) async throws -> PaymentResult {
        // Redirect to 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)
    }
}

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

Strategy in SwiftUI — the pattern integrates naturally with MVVM. A ViewModel contains a strategy property and calls its method upon user action. SwiftUI View receives data via @Published or @State — the strategy hides implementation details from the View. For example, a text validation strategy (emailValidator, phoneValidator) is swapped depending on the input field type. Combining Strategy with SwiftUI provides flexibility without inheriting from UIKit.

Strategy in Android: Kotlin Implementation with Interface

Strategy in Kotlin uses Interface at the language level and functional interfaces (SAM) for simplification. Kotlin supports lambdas, allowing algorithms to be passed as functions without declaring a separate strategy class. In Android, the pattern is used in ViewModel and Use Cases for isolating data loading, caching and error handling algorithms. Android projects with Clean Architecture use Strategy for injecting different repository implementations depending on flags (mock, real, cache).

kotlin
// 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 {
        // Banking 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)
    }
}

// Usage in ViewModel
class CheckoutViewModel : ViewModel() {
    private var processor = PaymentProcessor(CardPaymentStrategy("4111...", "123"))

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

Strategy with Hilt/Dagger — in Android projects, strategies are often injected via DI. Hilt provides a concrete PaymentStrategy implementation through @Binds or @Provides. This allows changing the strategy without modifying the context code — simply change the DI module for another build (debug/release). For example, MockPaymentStrategy is injected for debugging, a real banking strategy for production. The combination of Strategy + DI provides maximum flexibility.

Comparing Strategy with State, Command and Template Method

Strategy vs State — structurally the patterns are identical: both use composition with an interface and concrete classes. The difference is in purpose: Strategy selects an independent algorithm, State controls object behavior depending on its state. In State, the context itself changes the strategy when the state changes; in Strategy, the context does not control switching — the client explicitly sets the algorithm. Strategies do not know about each other, while states can transition between each other.

Strategy vs Command — Command encapsulates a single action as an object, Strategy encapsulates a set of interchangeable algorithms. Command is "what to do" (a single execute call), Strategy is "how to do it" (an algorithm of several steps). Command is used for queues, deferred execution, undo/redo. Strategy is used for choosing how to perform a task at runtime. Commands can be parameterized with strategies, combining both patterns.

CharacteristicStrategyStateCommandTemplate Method
PurposeInterchangeable algorithmsBehavior from stateRequest encapsulationAlgorithm skeleton
SwitchingExplicitly by clientAutomatically by contextBy client or queueBy inheritance
LevelObject (composition)Object (composition)ObjectClass (inheritance)

Strategy vs Template Method — both patterns define algorithms but in different ways. Template Method uses inheritance: a base class defines the algorithm skeleton (template method), subclasses override individual steps. Strategy uses composition: the algorithm is fully externalized to a separate class. Template Method is simpler for cases with a fixed algorithm structure, Strategy — when algorithms are completely different and can change dynamically.

Real-World Examples of Using the Strategy Pattern

Payment Processing — the classic Strategy example. An online store shopping cart contains a list of items, and the payment method is chosen by the user. Each method (card, PayPal, Apple Pay, Google Pay, cryptocurrency) is a separate strategy with a common pay(amount) signature. The PaymentProcessor context does not know how exactly the payment is processed — it calls the common method. Adding a new payment method does not require changing the cart code.

Data Validation — Strategy is used for different validation rules of the same field. EmailValidatorStrategy, PhoneValidatorStrategy, AgeValidatorStrategy implement a common ValidationStrategy interface with a validate(input) method. A registration form uses a set of strategies to check each field. Validation strategies can be combined in a chain (Chain of Responsibility) or applied all at once in a loop. This replaces long if-else checks with a collection of polymorphic validators.

swift
// Sorting strategy
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) }
}

Authentication — in mobile apps, authentication strategies are switched depending on the provider. AuthStrategy with login(), logout(), getToken() methods is implemented for EmailPasswordAuth, GoogleAuth, AppleAuth, BiometricAuth. The AuthManager context accepts the strategy via DI or factory. This allows adding new authentication providers without changing the login screen. The Strategy pattern is the foundation for many OAuth libraries and Firebase Authentication.

Frequently Asked Questions

When should I use Strategy instead of if-else?

Strategy is justified when you have 3+ algorithms that may change or expand. If there are 2 algorithms and they are stable — a simple if-else is less costly. Use Strategy when algorithms are used in different parts of the application, when you need to swap algorithms at runtime, or when each algorithm requires its own dependencies and tests.

Is Strategy the same as State?

No, these are different patterns with a similar structure. Strategy — the client explicitly selects an algorithm, and strategies are independent. State — the object itself changes its behavior when its internal state changes, and states can transition into each other. In State, the context manages state changes; in Strategy, the client code does.

Can Strategy be used without classes — via closures?

Yes, in Swift and Kotlin a strategy can be passed as a closure or lambda. Swift: typealias PaymentHandler = (Decimal) async throws -> PaymentResult. Kotlin: typealias PaymentFun = suspend (BigDecimal) -> PaymentResult. This simplifies code for simple cases but loses naming and documentation. For 1-2 algorithms, a closure is enough; for 4+, separate classes are better.

How to test the Strategy pattern?

Each strategy is tested with a separate unit test using mock dependencies. The Context is tested with a mock strategy — verifying that the context calls the strategy method and passes the correct parameters. In Swift, use XCTest + protocols for mocks; in Kotlin, use MockK or Mockito. The main advantage: each strategy is tested in isolation without complex setup.

Is Strategy a GoF pattern?

Yes, Strategy is one of the 23 patterns described in the book "Design Patterns: Elements of Reusable Object-Oriented Software" (Gamma, Helm, Johnson, Vlissides, 1994). It belongs to the group of behavioral patterns. Aliases: Policy. The original Smalltalk-80 example code is available in the original GoF edition.

Summary

  • Strategy — a behavioral pattern for interchangeable algorithms with a common interface
  • Encapsulation — each algorithm is isolated in a separate strategy class
  • iOS Swift — implementation via Protocol, structs and closures
  • Android Kotlin — implementation via Interface, lambdas and DI with Hilt
  • Open/Closed Principle — new strategies are added without changing the context
  • Applications — payments, validation, sorting, authentication, formatting

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also