Behavioral Design Pattern · Java

Strategy Design Pattern in Java

Select an algorithm’s behavior at runtime by making a family of algorithms interchangeable.

What is Strategy?

The Strategy design pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. This lets the algorithm vary independently from the clients that use it. In a payment processing system, the payment context works with different payment gateways, while each gateway encapsulates its own payment-processing algorithm.

Beginner-friendly Java example

Focus on the roles in the example first. The pattern becomes easier when you can identify the sender, receiver, context, state, or strategy involved.

// Step 1: Define the PaymentStrategy interface
interface PaymentStrategy {
    void processPayment(double amount);
}

// Step 2: Implement concrete payment strategies
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) {
        // Process payment using PayPal API
        System.out.println("Payment processed via PayPal: $" + amount);
    }
}

class StripeStrategy implements PaymentStrategy {
    private String apiKey;

    public StripeStrategy(String apiKey) {
        this.apiKey = apiKey;
    }

    @Override
    public void processPayment(double amount) {
        // Process payment using Stripe API
        System.out.println("Payment processed via Stripe: $" + amount);
    }
}

// Step 3: Define the PaymentContext
class PaymentContext {
    private PaymentStrategy paymentStrategy;

    public PaymentContext(PaymentStrategy paymentStrategy) {
        this.paymentStrategy = paymentStrategy;
    }

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

    public void processPayment(double amount) {
        paymentStrategy.processPayment(amount);
    }
}

// Step 4: Test the implementation
public class Main {
    public static void main(String[] args) {
        PaymentStrategy paypalStrategy = new PayPalStrategy("paypal@example.com", "password");
        PaymentContext paymentContext = new PaymentContext(paypalStrategy);

        paymentContext.processPayment(100.0);

        PaymentStrategy stripeStrategy = new StripeStrategy("stripe-api-key");
        paymentContext.setPaymentStrategy(stripeStrategy);
        paymentContext.processPayment(150.0);
    }
}

Benefits and trade-offs

Benefits
  • Keeps responsibilities focused.
  • Reduces conditional and tightly coupled code.
  • Makes behavior easier to extend and test.
Trade-offs
  • Can introduce extra objects and interfaces.
  • Too many small classes can make simple logic harder to follow.

Real-time use cases

  • Payment gateway selection
  • Shipping price rules
  • Tax calculation strategies
  • Discount and pricing rules
  • Compression algorithms
Key takeawaySelect an algorithm’s behavior at runtime by making a family of algorithms interchangeable. Use it when behavior or communication is changing faster than the objects themselves.