Skip to main content

Strategy Pattern: Behavioral Design Pattern

The Strategy Pattern is a behavioral design pattern that enables selecting an algorithm's implementation at runtime. It defines a family of algorithms, encapsulates each one, and makes them interchangeable. This pattern lets the algorithm vary independently from clients that use it.

Overview

The Strategy Pattern is used when you have a family of similar algorithms and want to make them interchangeable. Instead of implementing a single algorithm directly, code receives run-time instructions as to which in a family of algorithms to use.

Key Concepts

  • Strategy Interface: Defines a common interface for all supported algorithms
  • Concrete Strategies: Implement the algorithm interface
  • Context: Uses a strategy object to call the algorithm
  • Client: Creates and configures the context with a specific strategy

Problem Statement

Consider a scenario where you have different payment methods in an e-commerce system:

// Without Strategy Pattern - Poor Design
class PaymentProcessor {
public void processPayment(String paymentType, double amount) {
if (paymentType.equals("credit")) {
// Credit card payment logic
System.out.println("Processing credit card payment: " + amount);
} else if (paymentType.equals("debit")) {
// Debit card payment logic
System.out.println("Processing debit card payment: " + amount);
} else if (paymentType.equals("paypal")) {
// PayPal payment logic
System.out.println("Processing PayPal payment: " + amount);
}
// Adding new payment methods requires modifying this class
}
}

Problems with this approach:

  • Violates Open/Closed Principle
  • Hard to maintain and extend
  • Tight coupling between payment logic and processor
  • Difficult to test individual payment methods

Solution with Strategy Pattern

UML Diagram

┌─────────────────┐    ┌─────────────────────┐
│ Strategy │ │ Context │
│ Interface │ │ │
├─────────────────┤ ├─────────────────────┤
│ + execute() │◄───│ - strategy │
└─────────────────┘ │ + setStrategy() │
▲ │ + executeStrategy() │
│ └─────────────────────┘

┌────┴────┐
│ │
┌─────────┐ ┌─────────┐
│StrategyA│ │StrategyB│
├─────────┤ ├─────────┤
│+execute()│ │+execute()│
└─────────┘ └─────────┘

Implementation

1. Strategy Interface

// PaymentStrategy.java
public interface PaymentStrategy {
void processPayment(double amount);
String getPaymentMethod();
}

2. Concrete Strategies

// CreditCardStrategy.java
public class CreditCardStrategy implements PaymentStrategy {
private String cardNumber;
private String cvv;
private String expiryDate;

public CreditCardStrategy(String cardNumber, String cvv, String expiryDate) {
this.cardNumber = cardNumber;
this.cvv = cvv;
this.expiryDate = expiryDate;
}

@Override
public void processPayment(double amount) {
System.out.println("Processing credit card payment of $" + amount);
System.out.println("Card Number: " + maskCardNumber(cardNumber));
// Credit card specific logic
validateCard();
chargeCard(amount);
}

@Override
public String getPaymentMethod() {
return "Credit Card";
}

private void validateCard() {
// Credit card validation logic
System.out.println("Validating credit card...");
}

private void chargeCard(double amount) {
// Credit card charging logic
System.out.println("Charging credit card: $" + amount);
}

private String maskCardNumber(String cardNumber) {
return "****-****-****-" + cardNumber.substring(cardNumber.length() - 4);
}
}

// DebitCardStrategy.java
public class DebitCardStrategy implements PaymentStrategy {
private String cardNumber;
private String pin;

public DebitCardStrategy(String cardNumber, String pin) {
this.cardNumber = cardNumber;
this.pin = pin;
}

@Override
public void processPayment(double amount) {
System.out.println("Processing debit card payment of $" + amount);
System.out.println("Card Number: " + maskCardNumber(cardNumber));
// Debit card specific logic
validatePin();
deductFromAccount(amount);
}

@Override
public String getPaymentMethod() {
return "Debit Card";
}

private void validatePin() {
// PIN validation logic
System.out.println("Validating PIN...");
}

private void deductFromAccount(double amount) {
// Account deduction logic
System.out.println("Deducting from account: $" + amount);
}

private String maskCardNumber(String cardNumber) {
return "****-****-****-" + cardNumber.substring(cardNumber.length() - 4);
}
}

// PayPalStrategy.java
public class PayPalStrategy implements PaymentStrategy {
private String email;
private String password;

public PayPalStrategy(String email, String password) {
this.email = email;
this.password = password;
}

@Override
public void processPayment(double amount) {
System.out.println("Processing PayPal payment of $" + amount);
System.out.println("PayPal Email: " + email);
// PayPal specific logic
authenticatePayPal();
transferPayPal(amount);
}

@Override
public String getPaymentMethod() {
return "PayPal";
}

private void authenticatePayPal() {
// PayPal authentication logic
System.out.println("Authenticating PayPal account...");
}

private void transferPayPal(double amount) {
// PayPal transfer logic
System.out.println("Transferring via PayPal: $" + amount);
}
}

3. Context Class

// PaymentProcessor.java
public class PaymentProcessor {
private PaymentStrategy paymentStrategy;

public void setPaymentStrategy(PaymentStrategy strategy) {
this.paymentStrategy = strategy;
}

public void processPayment(double amount) {
if (paymentStrategy == null) {
throw new IllegalStateException("Payment strategy not set");
}

System.out.println("Using payment method: " + paymentStrategy.getPaymentMethod());
paymentStrategy.processPayment(amount);
System.out.println("Payment processed successfully!");
}
}

4. Client Usage

// Client.java
public class Client {
public static void main(String[] args) {
PaymentProcessor processor = new PaymentProcessor();

// Credit Card Payment
PaymentStrategy creditCard = new CreditCardStrategy("1234567890123456", "123", "12/25");
processor.setPaymentStrategy(creditCard);
processor.processPayment(100.0);

System.out.println("\n" + "=".repeat(50) + "\n");

// Debit Card Payment
PaymentStrategy debitCard = new DebitCardStrategy("9876543210987654", "4567");
processor.setPaymentStrategy(debitCard);
processor.processPayment(50.0);

System.out.println("\n" + "=".repeat(50) + "\n");

// PayPal Payment
PaymentStrategy paypal = new PayPalStrategy("[email protected]", "password123");
processor.setPaymentStrategy(paypal);
processor.processPayment(75.0);
}
}

Implementation in Different Languages

Python Implementation

from abc import ABC, abstractmethod
from typing import Protocol

# Strategy Interface (using Protocol for structural typing)
class PaymentStrategy(Protocol):
def process_payment(self, amount: float) -> None:
...

def get_payment_method(self) -> str:
...

# Concrete Strategies
class CreditCardStrategy:
def __init__(self, card_number: str, cvv: str, expiry_date: str):
self.card_number = card_number
self.cvv = cvv
self.expiry_date = expiry_date

def process_payment(self, amount: float) -> None:
print(f"Processing credit card payment of ${amount}")
print(f"Card Number: {self._mask_card_number()}")
self._validate_card()
self._charge_card(amount)

def get_payment_method(self) -> str:
return "Credit Card"

def _validate_card(self) -> None:
print("Validating credit card...")

def _charge_card(self, amount: float) -> None:
print(f"Charging credit card: ${amount}")

def _mask_card_number(self) -> str:
return f"****-****-****-{self.card_number[-4:]}"

class PayPalStrategy:
def __init__(self, email: str, password: str):
self.email = email
self.password = password

def process_payment(self, amount: float) -> None:
print(f"Processing PayPal payment of ${amount}")
print(f"PayPal Email: {self.email}")
self._authenticate_paypal()
self._transfer_paypal(amount)

def get_payment_method(self) -> str:
return "PayPal"

def _authenticate_paypal(self) -> None:
print("Authenticating PayPal account...")

def _transfer_paypal(self, amount: float) -> None:
print(f"Transferring via PayPal: ${amount}")

# Context
class PaymentProcessor:
def __init__(self):
self._payment_strategy: PaymentStrategy = None

def set_payment_strategy(self, strategy: PaymentStrategy) -> None:
self._payment_strategy = strategy

def process_payment(self, amount: float) -> None:
if self._payment_strategy is None:
raise ValueError("Payment strategy not set")

print(f"Using payment method: {self._payment_strategy.get_payment_method()}")
self._payment_strategy.process_payment(amount)
print("Payment processed successfully!")

# Client Usage
def main():
processor = PaymentProcessor()

# Credit Card Payment
credit_card = CreditCardStrategy("1234567890123456", "123", "12/25")
processor.set_payment_strategy(credit_card)
processor.process_payment(100.0)

print("\n" + "=" * 50 + "\n")

# PayPal Payment
paypal = PayPalStrategy("[email protected]", "password123")
processor.set_payment_strategy(paypal)
processor.process_payment(75.0)

if __name__ == "__main__":
main()

Go Implementation

package main

import (
"fmt"
"strings"
)

// Strategy Interface
type PaymentStrategy interface {
ProcessPayment(amount float64)
GetPaymentMethod() string
}

// Concrete Strategies
type CreditCardStrategy struct {
cardNumber string
cvv string
expiryDate string
}

func NewCreditCardStrategy(cardNumber, cvv, expiryDate string) *CreditCardStrategy {
return &CreditCardStrategy{
cardNumber: cardNumber,
cvv: cvv,
expiryDate: expiryDate,
}
}

func (c *CreditCardStrategy) ProcessPayment(amount float64) {
fmt.Printf("Processing credit card payment of $%.2f\n", amount)
fmt.Printf("Card Number: %s\n", c.maskCardNumber())
c.validateCard()
c.chargeCard(amount)
}

func (c *CreditCardStrategy) GetPaymentMethod() string {
return "Credit Card"
}

func (c *CreditCardStrategy) validateCard() {
fmt.Println("Validating credit card...")
}

func (c *CreditCardStrategy) chargeCard(amount float64) {
fmt.Printf("Charging credit card: $%.2f\n", amount)
}

func (c *CreditCardStrategy) maskCardNumber() string {
if len(c.cardNumber) < 4 {
return "****"
}
return "****-****-****-" + c.cardNumber[len(c.cardNumber)-4:]
}

type PayPalStrategy struct {
email string
password string
}

func NewPayPalStrategy(email, password string) *PayPalStrategy {
return &PayPalStrategy{
email: email,
password: password,
}
}

func (p *PayPalStrategy) ProcessPayment(amount float64) {
fmt.Printf("Processing PayPal payment of $%.2f\n", amount)
fmt.Printf("PayPal Email: %s\n", p.email)
p.authenticatePayPal()
p.transferPayPal(amount)
}

func (p *PayPalStrategy) GetPaymentMethod() string {
return "PayPal"
}

func (p *PayPalStrategy) authenticatePayPal() {
fmt.Println("Authenticating PayPal account...")
}

func (p *PayPalStrategy) transferPayPal(amount float64) {
fmt.Printf("Transferring via PayPal: $%.2f\n", amount)
}

// Context
type PaymentProcessor struct {
paymentStrategy PaymentStrategy
}

func NewPaymentProcessor() *PaymentProcessor {
return &PaymentProcessor{}
}

func (p *PaymentProcessor) SetPaymentStrategy(strategy PaymentStrategy) {
p.paymentStrategy = strategy
}

func (p *PaymentProcessor) ProcessPayment(amount float64) error {
if p.paymentStrategy == nil {
return fmt.Errorf("payment strategy not set")
}

fmt.Printf("Using payment method: %s\n", p.paymentStrategy.GetPaymentMethod())
p.paymentStrategy.ProcessPayment(amount)
fmt.Println("Payment processed successfully!")
return nil
}

// Client Usage
func main() {
processor := NewPaymentProcessor()

// Credit Card Payment
creditCard := NewCreditCardStrategy("1234567890123456", "123", "12/25")
processor.SetPaymentStrategy(creditCard)
processor.ProcessPayment(100.0)

fmt.Println("\n" + strings.Repeat("=", 50) + "\n")

// PayPal Payment
paypal := NewPayPalStrategy("[email protected]", "password123")
processor.SetPaymentStrategy(paypal)
processor.ProcessPayment(75.0)
}

JavaScript/TypeScript Implementation

// Strategy Interface
interface PaymentStrategy {
processPayment(amount: number): void;
getPaymentMethod(): string;
}

// Concrete Strategies
class CreditCardStrategy implements PaymentStrategy {
private cardNumber: string;
private cvv: string;
private expiryDate: string;

constructor(cardNumber: string, cvv: string, expiryDate: string) {
this.cardNumber = cardNumber;
this.cvv = cvv;
this.expiryDate = expiryDate;
}

processPayment(amount: number): void {
console.log(`Processing credit card payment of $${amount}`);
console.log(`Card Number: ${this.maskCardNumber()}`);
this.validateCard();
this.chargeCard(amount);
}

getPaymentMethod(): string {
return "Credit Card";
}

private validateCard(): void {
console.log("Validating credit card...");
}

private chargeCard(amount: number): void {
console.log(`Charging credit card: $${amount}`);
}

private maskCardNumber(): string {
return `****-****-****-${this.cardNumber.slice(-4)}`;
}
}

class PayPalStrategy implements PaymentStrategy {
private email: string;
private password: string;

constructor(email: string, password: string) {
this.email = email;
this.password = password;
}

processPayment(amount: number): void {
console.log(`Processing PayPal payment of $${amount}`);
console.log(`PayPal Email: ${this.email}`);
this.authenticatePayPal();
this.transferPayPal(amount);
}

getPaymentMethod(): string {
return "PayPal";
}

private authenticatePayPal(): void {
console.log("Authenticating PayPal account...");
}

private transferPayPal(amount: number): void {
console.log(`Transferring via PayPal: $${amount}`);
}
}

// Context
class PaymentProcessor {
private paymentStrategy: PaymentStrategy | null = null;

setPaymentStrategy(strategy: PaymentStrategy): void {
this.paymentStrategy = strategy;
}

processPayment(amount: number): void {
if (!this.paymentStrategy) {
throw new Error("Payment strategy not set");
}

console.log(
`Using payment method: ${this.paymentStrategy.getPaymentMethod()}`
);
this.paymentStrategy.processPayment(amount);
console.log("Payment processed successfully!");
}
}

// Client Usage
function main(): void {
const processor = new PaymentProcessor();

// Credit Card Payment
const creditCard = new CreditCardStrategy("1234567890123456", "123", "12/25");
processor.setPaymentStrategy(creditCard);
processor.processPayment(100.0);

console.log("\n" + "=".repeat(50) + "\n");

// PayPal Payment
const paypal = new PayPalStrategy("[email protected]", "password123");
processor.setPaymentStrategy(paypal);
processor.processPayment(75.0);
}

main();

Advanced Strategy Pattern Examples

1. Sorting Strategy

// SortingStrategy.java
public interface SortingStrategy {
void sort(int[] array);
String getAlgorithmName();
}

// BubbleSortStrategy.java
public class BubbleSortStrategy implements SortingStrategy {
@Override
public void sort(int[] array) {
int n = array.length;
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (array[j] > array[j + 1]) {
int temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
}

@Override
public String getAlgorithmName() {
return "Bubble Sort";
}
}

// QuickSortStrategy.java
public class QuickSortStrategy implements SortingStrategy {
@Override
public void sort(int[] array) {
quickSort(array, 0, array.length - 1);
}

private void quickSort(int[] array, int low, int high) {
if (low < high) {
int pi = partition(array, low, high);
quickSort(array, low, pi - 1);
quickSort(array, pi + 1, high);
}
}

private int partition(int[] array, int low, int high) {
int pivot = array[high];
int i = low - 1;

for (int j = low; j < high; j++) {
if (array[j] <= pivot) {
i++;
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}

int temp = array[i + 1];
array[i + 1] = array[high];
array[high] = temp;

return i + 1;
}

@Override
public String getAlgorithmName() {
return "Quick Sort";
}
}

// Sorter.java (Context)
public class Sorter {
private SortingStrategy strategy;

public void setStrategy(SortingStrategy strategy) {
this.strategy = strategy;
}

public void sort(int[] array) {
if (strategy == null) {
throw new IllegalStateException("Sorting strategy not set");
}

System.out.println("Using algorithm: " + strategy.getAlgorithmName());
long startTime = System.currentTimeMillis();
strategy.sort(array);
long endTime = System.currentTimeMillis();

System.out.println("Sorting completed in " + (endTime - startTime) + "ms");
System.out.println("Sorted array: " + Arrays.toString(array));
}
}

2. Compression Strategy

// CompressionStrategy.java
public interface CompressionStrategy {
byte[] compress(byte[] data);
byte[] decompress(byte[] data);
String getAlgorithmName();
}

// GzipCompressionStrategy.java
public class GzipCompressionStrategy implements CompressionStrategy {
@Override
public byte[] compress(byte[] data) {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
GZIPOutputStream gzipOut = new GZIPOutputStream(baos)) {
gzipOut.write(data);
gzipOut.finish();
return baos.toByteArray();
} catch (IOException e) {
throw new RuntimeException("Gzip compression failed", e);
}
}

@Override
public byte[] decompress(byte[] data) {
try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
GZIPInputStream gzipIn = new GZIPInputStream(bais);
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {

byte[] buffer = new byte[1024];
int len;
while ((len = gzipIn.read(buffer)) > 0) {
baos.write(buffer, 0, len);
}
return baos.toByteArray();
} catch (IOException e) {
throw new RuntimeException("Gzip decompression failed", e);
}
}

@Override
public String getAlgorithmName() {
return "Gzip";
}
}

// Compressor.java (Context)
public class Compressor {
private CompressionStrategy strategy;

public void setStrategy(CompressionStrategy strategy) {
this.strategy = strategy;
}

public byte[] compress(byte[] data) {
if (strategy == null) {
throw new IllegalStateException("Compression strategy not set");
}

System.out.println("Using compression: " + strategy.getAlgorithmName());
return strategy.compress(data);
}

public byte[] decompress(byte[] data) {
if (strategy == null) {
throw new IllegalStateException("Compression strategy not set");
}

return strategy.decompress(data);
}
}

Use Cases and Applications

Common Use Cases

  1. Payment Processing: Different payment methods (credit card, PayPal, cryptocurrency)
  2. Sorting Algorithms: Different sorting strategies based on data characteristics
  3. Compression: Different compression algorithms (Gzip, Bzip2, LZMA)
  4. Caching: Different caching strategies (LRU, LFU, FIFO)
  5. Validation: Different validation strategies for different data types
  6. Logging: Different logging strategies (file, database, remote service)
  7. Authentication: Different authentication methods (OAuth, JWT, API key)
  8. Data Export: Different export formats (JSON, XML, CSV, PDF)

Real-World Examples

1. E-commerce Payment System

// Dynamic payment method selection based on user preference
public class CheckoutService {
private PaymentProcessor paymentProcessor;

public void checkout(Order order, String preferredPaymentMethod) {
PaymentStrategy strategy = PaymentStrategyFactory.createStrategy(preferredPaymentMethod);
paymentProcessor.setPaymentStrategy(strategy);
paymentProcessor.processPayment(order.getTotal());
}
}

2. Data Processing Pipeline

// Different processing strategies based on data type
public class DataProcessor {
private ProcessingStrategy strategy;

public void processData(Data data) {
ProcessingStrategy strategy = ProcessingStrategyFactory.createStrategy(data.getType());
this.strategy = strategy;
strategy.process(data);
}
}

Benefits and Advantages

✅ Advantages

  1. Open/Closed Principle: Easy to add new strategies without modifying existing code
  2. Single Responsibility: Each strategy class has a single responsibility
  3. Eliminates Conditional Statements: Replaces complex if-else chains
  4. Runtime Flexibility: Can change algorithms at runtime
  5. Testability: Each strategy can be tested independently
  6. Reusability: Strategies can be reused across different contexts
  7. Maintainability: Easy to maintain and modify individual strategies

❌ Disadvantages

  1. Increased Complexity: Can add complexity for simple scenarios
  2. Runtime Overhead: Slight performance overhead due to indirection
  3. Memory Usage: Each strategy is a separate object
  4. Learning Curve: Team needs to understand the pattern

Best Practices

1. Strategy Selection

// Strategy Factory Pattern
public class PaymentStrategyFactory {
public static PaymentStrategy createStrategy(String paymentType) {
switch (paymentType.toLowerCase()) {
case "credit":
return new CreditCardStrategy("", "", "");
case "debit":
return new DebitCardStrategy("", "");
case "paypal":
return new PayPalStrategy("", "");
case "crypto":
return new CryptoStrategy();
default:
throw new IllegalArgumentException("Unknown payment type: " + paymentType);
}
}
}

2. Strategy with Parameters

// Strategy with configuration
public class ConfigurablePaymentStrategy implements PaymentStrategy {
private final PaymentConfig config;

public ConfigurablePaymentStrategy(PaymentConfig config) {
this.config = config;
}

@Override
public void processPayment(double amount) {
// Use config for processing
if (config.isTestMode()) {
// Test mode logic
} else {
// Production mode logic
}
}
}

3. Strategy with State

// Strategy that maintains state
public class StatefulPaymentStrategy implements PaymentStrategy {
private int transactionCount = 0;
private double totalAmount = 0.0;

@Override
public void processPayment(double amount) {
transactionCount++;
totalAmount += amount;
// Process payment logic
}

public PaymentStatistics getStatistics() {
return new PaymentStatistics(transactionCount, totalAmount);
}
}

Testing Strategies

Unit Testing

// Testing individual strategies
@Test
public void testCreditCardStrategy() {
PaymentStrategy strategy = new CreditCardStrategy("1234567890123456", "123", "12/25");

// Mock or capture output
ByteArrayOutputStream output = new ByteArrayOutputStream();
System.setOut(new PrintStream(output));

strategy.processPayment(100.0);

String result = output.toString();
assertTrue(result.contains("Processing credit card payment"));
assertTrue(result.contains("$100.0"));
}

// Testing context with mock strategy
@Test
public void testPaymentProcessor() {
PaymentProcessor processor = new PaymentProcessor();
MockPaymentStrategy mockStrategy = new MockPaymentStrategy();

processor.setPaymentStrategy(mockStrategy);
processor.processPayment(50.0);

assertTrue(mockStrategy.isProcessPaymentCalled());
assertEquals(50.0, mockStrategy.getLastAmount(), 0.01);
}

Integration Testing

@Test
public void testPaymentFlow() {
PaymentProcessor processor = new PaymentProcessor();
PaymentStrategy creditCard = new CreditCardStrategy("1234567890123456", "123", "12/25");

processor.setPaymentStrategy(creditCard);

// Test complete payment flow
assertDoesNotThrow(() -> processor.processPayment(100.0));
}

Performance Considerations

1. Strategy Caching

// Cache frequently used strategies
public class StrategyCache {
private static final Map<String, PaymentStrategy> cache = new ConcurrentHashMap<>();

public static PaymentStrategy getStrategy(String type) {
return cache.computeIfAbsent(type, PaymentStrategyFactory::createStrategy);
}
}

2. Lazy Loading

// Load strategies on demand
public class LazyPaymentProcessor {
private volatile PaymentStrategy strategy;
private final String strategyType;

public LazyPaymentProcessor(String strategyType) {
this.strategyType = strategyType;
}

public void processPayment(double amount) {
if (strategy == null) {
synchronized (this) {
if (strategy == null) {
strategy = PaymentStrategyFactory.createStrategy(strategyType);
}
}
}
strategy.processPayment(amount);
}
}

1. Strategy vs. Template Method

  • Strategy: Different algorithms are completely different
  • Template Method: Different implementations of the same algorithm

2. Strategy vs. State

  • Strategy: Algorithm selection based on external factors
  • State: Behavior changes based on internal state

3. Strategy vs. Command

  • Strategy: Focuses on algorithm selection
  • Command: Focuses on encapsulating requests as objects

Conclusion

The Strategy Pattern is a powerful behavioral design pattern that promotes flexibility, maintainability, and testability in your code. It's particularly useful when you have a family of similar algorithms that need to be interchangeable at runtime.

When to Use Strategy Pattern

  • ✅ You have a family of similar algorithms
  • ✅ You want to avoid complex conditional statements
  • ✅ You need to change algorithms at runtime
  • ✅ You want to isolate algorithm implementation from client code
  • ✅ You want to make your code more testable

When Not to Use Strategy Pattern

  • ❌ You have only one algorithm (over-engineering)
  • ❌ The algorithms are too different to share a common interface
  • ❌ The performance overhead is critical
  • ❌ The algorithms are simple and unlikely to change

The Strategy Pattern is a fundamental pattern that every developer should understand and be able to implement effectively. It's widely used in frameworks, libraries, and enterprise applications to provide flexible and extensible solutions.

Resources

Official Documentation

Code Examples