Sep
21
2023

Demystifying Design Patterns: Strategy Design Pattern

Do-we-really-need-developers-img
  1. Demystifying Design Patterns: Singleton Design Pattern
  2. Demystifying Design Patterns: Factory Method Design Pattern
  3. Demystifying Design Patterns: Abstract Factory Design Pattern
  4. Demystifying Design Patterns: Builder Design Pattern
  5. Demystifying Design Patterns: Prototype Design Pattern
  6. Demystifying Design Patterns: Adapter Design Pattern
  7. Demystifying Design Patterns: Bridge Design Pattern
  8. Demystifying Design Patterns: Composite Design Pattern
  9. Demystifying Design Patterns: Decorator Design Pattern
  10. Demystifying Design Patterns: Proxy Design Pattern
  11. Demystifying Design Patterns: Observer Design Pattern
  12. Demystifying Design Patterns: Strategy Design Pattern
  13. Demystifying Design Patterns: Command Design Pattern
  14. Demystifying Design Patterns: State Design Pattern
  15. Demystifying Design Patterns: Chain of Responsibility Design Pattern
  16. Demystifying Design Patterns: Visitor Design Pattern
  17. Demystifying Design Patterns: Template Method Design Pattern

Welcome to the 12th installment of our series on Demystifying Design Patterns! In this article, we dive deep into the Strategy Design Pattern. This powerful behavioral pattern enables you to define a family of algorithms, encapsulate each one, and make them interchangeable. With the Strategy Pattern, you can switch algorithms at runtime, leading to flexible and maintainable code. Join us as we explore the intricacies of this pattern, its use cases, real-life examples, and provide code implementations in Java, C#, and Python.

Defining Strategies with the Strategy Pattern

At its core, the Strategy Pattern defines a family of algorithms, encapsulates each one as a separate class, and makes them interchangeable. It allows you to select an algorithm from the family at runtime, without altering the client code that uses these algorithms. This promotes code reuse and flexibility.

Switching Algorithms at Runtime

One of the primary advantages of the Strategy Pattern is the ability to switch algorithms dynamically. This is incredibly useful when you have different algorithms to accomplish a task, and you want to choose the most suitable one based on changing conditions or user preferences. By decoupling the algorithm from the client code, you can swap strategies seamlessly.

Strategy vs. State Pattern

While the Strategy and State Patterns may seem similar at first glance, they serve different purposes. The Strategy Pattern focuses on varying algorithms independently, allowing you to choose one at runtime. In contrast, the State Pattern manages the state of an object and changes its behavior as its internal state changes. Understanding when to apply each pattern is crucial for effective design.

Strategy Pattern in Game Development

Game development is a fertile ground for the Strategy Pattern. Consider a video game where characters have different combat strategies. By implementing each strategy as a separate class and allowing characters to switch between them, you achieve flexibility and maintainability. From aggressive melee attacks to cautious ranged attacks, the Strategy Pattern empowers game developers to create dynamic gameplay experiences.

Real-Life Examples

Example 1: Payment Processing

In an e-commerce application, payment processing can vary depending on user preferences. You can implement payment strategies like credit card, PayPal, or cryptocurrency as separate classes. At checkout, the user can select their preferred payment method, and the appropriate strategy handles the payment process.

Example 2: Sorting Algorithms

Sorting is a fundamental operation in computer science. You can use the Strategy Pattern to encapsulate different sorting algorithms (e.g., quicksort, mergesort, bubblesort) into separate classes. Depending on the size and nature of the data to be sorted, you can switch between strategies to optimize performance.

Code Examples

Now, let’s explore the Strategy Pattern with code examples in Java, C#, and Python to illustrate its implementation.

example-strategy-design-pattern

Java Example:
// (Java code example illustrating the Strategy Pattern)
// Strategy interface
interface PaymentStrategy {
    void pay(int amount);
}

// Concrete strategy classes
class CreditCardPayment implements PaymentStrategy {
    private String cardNumber;

    public CreditCardPayment(String cardNumber) {
        this.cardNumber = cardNumber;
    }

    @Override
    public void pay(int amount) {
        System.out.println("Paid " + amount + " dollars using Credit Card with number " + cardNumber);
    }
}

class PayPalPayment implements PaymentStrategy {
    private String email;

    public PayPalPayment(String email) {
        this.email = email;
    }

    @Override
    public void pay(int amount) {
        System.out.println("Paid " + amount + " dollars using PayPal with email " + email);
    }
}

// Context class
class ShoppingCart {
    private PaymentStrategy paymentStrategy;

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

    public void checkout(int amount) {
        paymentStrategy.pay(amount);
    }
}

public class StrategyPatternDemo {
    public static void main(String[] args) {
        ShoppingCart cart = new ShoppingCart();

        // Customer selects payment method
        PaymentStrategy creditCardPayment = new CreditCardPayment("1234-5678-9876-5432");
        PaymentStrategy payPalPayment = new PayPalPayment("[email protected]");

        cart.setPaymentStrategy(creditCardPayment);
        cart.checkout(100);

        cart.setPaymentStrategy(payPalPayment);
        cart.checkout(50);
    }
}
C# Example:
// (C# code example illustrating the Strategy Pattern)
using System;

// Strategy interface
interface IPaymentStrategy
{
    void Pay(int amount);
}

// Concrete strategy classes
class CreditCardPayment : IPaymentStrategy
{
    private string cardNumber;

    public CreditCardPayment(string cardNumber)
    {
        this.cardNumber = cardNumber;
    }

    public void Pay(int amount)
    {
        Console.WriteLine($"Paid {amount} dollars using Credit Card with number {cardNumber}");
    }
}

class PayPalPayment : IPaymentStrategy
{
    private string email;

    public PayPalPayment(string email)
    {
        this.email = email;
    }

    public void Pay(int amount)
    {
        Console.WriteLine($"Paid {amount} dollars using PayPal with email {email}");
    }
}

// Context class
class ShoppingCart
{
    private IPaymentStrategy paymentStrategy;

    public void SetPaymentStrategy(IPaymentStrategy paymentStrategy)
    {
        this.paymentStrategy = paymentStrategy;
    }

    public void Checkout(int amount)
    {
        paymentStrategy.Pay(amount);
    }
}

class StrategyPatternDemo
{
    static void Main(string[] args)
    {
        ShoppingCart cart = new ShoppingCart();

        // Customer selects payment method
        IPaymentStrategy creditCardPayment = new CreditCardPayment("1234-5678-9876-5432");
        IPaymentStrategy payPalPayment = new PayPalPayment("[email protected]");

        cart.SetPaymentStrategy(creditCardPayment);
        cart.Checkout(100);

        cart.SetPaymentStrategy(payPalPayment);
        cart.Checkout(50);
    }
}
Python Example:
# (Python code example illustrating the Strategy Pattern)
# Strategy interface
class PaymentStrategy:
    def pay(self, amount):
        pass

# Concrete strategy classes
class CreditCardPayment(PaymentStrategy):
    def __init__(self, card_number):
        self.card_number = card_number

    def pay(self, amount):
        print(f"Paid {amount} dollars using Credit Card with number {self.card_number}")

class PayPalPayment(PaymentStrategy):
    def __init__(self, email):
        self.email = email

    def pay(self, amount):
        print(f"Paid {amount} dollars using PayPal with email {self.email}")

# Context class
class ShoppingCart:
    def __init__(self):
        self.payment_strategy = None

    def set_payment_strategy(self, payment_strategy):
        self.payment_strategy = payment_strategy

    def checkout(self, amount):
        self.payment_strategy.pay(amount)

# Client code
if __name__ == "__main__":
    cart = ShoppingCart()

    # Customer selects payment method
    credit_card_payment = CreditCardPayment("1234-5678-9876-5432")
    paypal_payment = PayPalPayment("[email protected]")

    cart.set_payment_strategy(credit_card_payment)
    cart. Checkout(100)

    cart.set_payment_strategy(paypal_payment)
    cart.checkout(50)

Conclusion

The Strategy Design Pattern is a versatile tool for managing interchangeable algorithms and promoting runtime flexibility in your software. By encapsulating algorithms as separate classes, you can switch strategies without modifying client code, resulting in cleaner and more maintainable systems.

In this article, we explored the Strategy Pattern’s key concepts, its ability to switch algorithms at runtime, and differentiated it from the State Pattern. We also delved into its application in real-life scenarios, such as payment processing and sorting algorithms. Additionally, we provided code examples in Java, C#, and Python to help you implement the pattern in your projects.

With the Strategy Pattern in your toolkit, you have the means to make your software more adaptable and responsive to changing requirements. Stay tuned for the next installment in our Demystifying Design Patterns series!