×

Search anything:

Strategy Design Pattern in Software Design [Java]

Internship at OpenGenus

Get this book -> Problems on Array: For Interviews and Competitive Programming

In the dynamic realm of software development, creating flexible and maintainable code is paramount. The ability to adapt to changing requirements, accommodate diverse functionalities, and simplify code maintenance are perpetual challenges faced by developers. One valuable tool in addressing these challenges is the Strategy Pattern.

Table of contents:

  1. Understanding the Strategy Pattern
  2. Core Components
  3. Real World Analogy
  4. Define a family of algorithms
  5. Implement concrete strategies
  6. Use composition to set the strategy
  7. Benefits of the Strategy Pattern
  8. Implementation of Strategy Pattern
  9. Real-Life Applications
  10. Conclusion

Understanding the Strategy Pattern

The Strategy Pattern is a behavioral design pattern that falls under the Gang of Four's (GoF) design patterns. It provides a systematic way to define a family of algorithms, encapsulate each algorithm, and make them interchangeable. The key idea is to allow a client to choose from a family of algorithms at runtime, promoting flexibility and ease of modification.

Core Components

1. Context

The 'Context' class represents the client that interacts with different strategies. It contains a reference to a strategy object and can switch between strategies dynamically.

2. Strategy

The 'Strategy' interface defines the common set of operations that concrete strategies must implement. Each concrete strategy encapsulates a specific algorithm.

3. Concrete Strategies

Concrete strategy classes implement the Strategy interface and provide specific implementations of the algorithm defined by the interface.

Real World Analogy

Here's a simple example to illustrate the Strategy Pattern:

Imagine you have a game where characters can fight each other. In the game, you have different strategies for fighting: sword fighting, gun fighting, and magic fighting.

In traditional programming, you might have a single Character class with a method like fight(). However, as the game evolves, adding new fighting strategies or modifying existing ones can become challenging and may lead to a complex and tightly coupled codebase.

Enter the Strategy Pattern:

Define a family of algorithms:

Create an interface (let's call it FightStrategy) that declares the method fight().

interface FightStrategy {
    void fight();
}

Implement concrete strategies:

Implement different strategies for fighting, each implementing the FightStrategy interface.

class SwordFight implements FightStrategy {
    @Override
    public void fight() {
        System.out.println("Using a sword to fight!");
    }
}

class GunFight implements FightStrategy {
    @Override
    public void fight() {
        System.out.println("Using a gun to fight!");
    }
}

class MagicFight implements FightStrategy {
    @Override
    public void fight() {
        System.out.println("Casting magic to fight!");
    }
}

//This is called function overridding (multiple functions with the same name and same args but different outcomes)

Use composition to set the strategy:

In your Character class, include a field for the chosen strategy and a method to set the strategy.

class Character {
    private FightStrategy fightStrategy;

    public void setFightStrategy(FightStrategy fightStrategy) {
        this.fightStrategy = fightStrategy;
    }

    public void performFight() {
        if (fightStrategy != null) {
            fightStrategy.fight();
        } else {
            System.out.println("No fight strategy set!");
        }
    }
}

Now, in the game, you can create a character and dynamically change its fighting strategy at runtime:

Character character = new Character();

character.setFightStrategy(new SwordFight());
character.performFight();  // Output: Using a sword to fight!

character.setFightStrategy(new GunFight());
character.performFight();  // Output: Using a gun to fight!

character.setFightStrategy(new MagicFight());
character.performFight();  // Output: Casting magic to fight!

This example demonstrates how the Strategy Pattern allows you to encapsulate and switch between different algorithms dynamically. It promotes flexibility, maintainability, and ease of adding new strategies without modifying existing code.

Benefits of the Strategy Pattern

1. Encapsulation of Algorithms

The Strategy Pattern excels in encapsulating algorithms. Each strategy is contained within its own class, isolating the implementation details from the Context. This encapsulation fosters code modularity and makes it easier to comprehend and maintain.

2. Flexibility and Extensibility

One of the primary advantages of the Strategy Pattern is its ability to accommodate changes seamlessly. New strategies can be added or existing ones modified without altering the Context class. This flexibility is particularly beneficial in evolving software systems where requirements are subject to change.

3. Improved Code Readability

By segregating algorithms into distinct strategy classes, the overall code becomes more readable and understandable. Developers can focus on individual strategies without being overwhelmed by a complex codebase.

4. Easy Testing and Debugging

Each strategy can be tested independently, facilitating unit testing and easing the debugging process. Isolating the strategies reduces the scope of potential issues, making it simpler to identify and rectify problems.

Implementation of Strategy Pattern

Let's look at a practical example to see the Strategy Pattern in action. Consider a system where a document processing application needs to support multiple compression algorithms. The goal is to compress a document using various strategies without modifying the core functionality of the application.

1. Strategy Interface

// Strategy Interface
public interface CompressionStrategy {
    void compress(String file);
}

2. Concrete Strategies

// Concrete Strategies
public class ZipCompressionStrategy implements CompressionStrategy {
    @Override
    public void compress(String file) {
        System.out.println("Compressing file using ZIP compression");
        // ZIP compression logic
    }
}

public class RarCompressionStrategy implements CompressionStrategy {
    @Override
    public void compress(String file) {
        System.out.println("Compressing file using RAR compression");
        // RAR compression logic
    }
}

public class GzipCompressionStrategy implements CompressionStrategy {
    @Override
    public void compress(String file) {
        System.out.println("Compressing file using GZIP compression");
        // GZIP compression logic
    }
}

3. Context

// Context
public class CompressionContext {
    private CompressionStrategy compressionStrategy;

    // Set strategy dynamically
    public void setCompressionStrategy(CompressionStrategy compressionStrategy) {
        this.compressionStrategy = compressionStrategy;
    }

    // Execute compression using the chosen strategy
    public void compressFile(String file) {
        compressionStrategy.compress(file);
    }
}

4. Client Usage

public class Client {
    public static void main(String[] args) {
        CompressionContext context = new CompressionContext();

        // Set ZIP compression strategy
        context.setCompressionStrategy(new ZipCompressionStrategy());
        context.compressFile("document.txt");

        // Set RAR compression strategy
        context.setCompressionStrategy(new RarCompressionStrategy());
        context.compressFile("document.txt");

        // Set GZIP compression strategy
        context.setCompressionStrategy(new GzipCompressionStrategy());
        context.compressFile("document.txt");
    }
}

In this example, the CompressionContext acts as the Context, while the CompressionStrategy interface and its concrete implementations (ZipCompressionStrategy, RarCompressionStrategy, and GzipCompressionStrategy) represent the various compression algorithms. The client can dynamically switch between compression strategies without modifying the core logic.

Real-Life Applications

1. Sorting Algorithms

In scenarios where different sorting algorithms are required, the Strategy Pattern can be applied. The Context class would represent the sorting task, and various sorting algorithms (bubble sort, quicksort, mergesort) could be encapsulated as separate strategies.

2. Payment Gateways

E-commerce platforms often integrate multiple payment gateways. The Strategy Pattern allows the system to support different payment providers without tightly coupling the payment processing code with a specific provider.

3. Image Processing

In image processing applications, strategies could be employed for different image filtering or enhancement algorithms. The Context class would manage the application of these strategies based on user preferences or application requirements.

Conclusion

The Strategy Pattern is a powerful ally in the software developer's toolkit, offering a systematic approach to handling varying algorithms and behaviors. Its ability to enhance code flexibility, maintainability, and readability makes it a valuable design pattern for addressing complex challenges in software design.

By adopting the Strategy Pattern, developers can build robust systems that gracefully adapt to changing requirements, promote modular code structures, and facilitate the incorporation of new functionalities

Strategy Design Pattern in Software Design [Java]
Share this