Skip to main content

Real World Implementation Strategy Design Pattern

Let's consider a real-world example of an application that performs image processing. The application could have various strategies for applying different kinds of filters like grayscale, sepia, or negative.

In this diagram, you can see the classes and their methods. Arrows are used to show relationships between classes. For example, the FilterStrategy interface is implemented by GrayscaleStrategy, SepiaStrategy, and NegativeStrategy classes, which is denoted by the arrows that point from these classes to FilterStrategy. The ImageProcessor class uses FilterStrategy, shown by the arrow from ImageProcessor to FilterStrategy.

Implementing the Strategy pattern:

First, define the FilterStrategy interface:

interface FilterStrategy {
apply(image: string): void;
}

Next, implement your concrete strategies:

class GrayscaleStrategy implements FilterStrategy {
apply(image: string): void {
console.log(`Applying grayscale filter to ${image}`);
}
}

class SepiaStrategy implements FilterStrategy {
apply(image: string): void {
console.log(`Applying sepia filter to ${image}`);
}
}

class NegativeStrategy implements FilterStrategy {
apply(image: string): void {
console.log(`Applying negative filter to ${image}`);
}
}

Then, create the context class:

class ImageProcessor {
private strategy: FilterStrategy;

constructor(strategy: FilterStrategy) {
this.strategy = strategy;
}

setFilterStrategy(strategy: FilterStrategy): void {
this.strategy = strategy;
}

applyFilter(image: string): void {
this.strategy.apply(image);
}
}

And here's how you can use it:

const imageProcessor = new ImageProcessor(new GrayscaleStrategy());
imageProcessor.applyFilter("Image1.jpg"); // Output: Applying grayscale filter to Image1.jpg

imageProcessor.setFilterStrategy(new SepiaStrategy());
imageProcessor.applyFilter("Image1.jpg"); // Output: Applying sepia filter to Image1.jpg

imageProcessor.setFilterStrategy(new NegativeStrategy());
imageProcessor.applyFilter("Image1.jpg"); // Output: Applying negative filter to Image1.jpg

In this example, ImageProcessor is the context, and it can use different filter strategies. This makes it easy to add new filters or change the filter for a given image at runtime, and each filter is isolated in its own class, making the code cleaner and easier to maintain.

Advanatages Of Strategy Pattern

The Strategy pattern offers a number of advantages:

Open/Closed Principle

This principle states that software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification. The Strategy pattern adheres to this principle. You can introduce new strategies without having to change the context. For example, if you want to add a new filter strategy, you just need to create a new class that implements the FilterStrategy interface.

class BrightnessStrategy implements FilterStrategy {
apply(image: string): void {
console.log(`Applying brightness filter to ${image}`);
}
}

This code demonstrates that we have added a new filter without touching any existing code.

Runtime Flexibility

Strategy pattern allows an algorithm's behavior to change at runtime. In the given example, the applyFilter method's behavior changes at runtime based on the filter strategy object.

imageProcessor.setFilterStrategy(new SepiaStrategy());
imageProcessor.applyFilter("Image1.jpg"); // Output: Applying sepia filter to Image1.jpg

imageProcessor.setFilterStrategy(new NegativeStrategy());
imageProcessor.applyFilter("Image1.jpg"); // Output: Applying negative filter to Image1.jpg

Here, we are changing the behavior of the applyFilter method at runtime by changing the filter strategy.

Code Organization

Strategy pattern helps organize code related to specific behavior in separate classes. It separates the complex or different parts of the code to be more understandable, maintainable, and flexible. All the code related to applying filters is separated into different classes (GrayscaleStrategy, SepiaStrategy, NegativeStrategy, etc.), making the code easier to understand and manage.

class SepiaStrategy implements FilterStrategy {
apply(image: string): void {
console.log(`Applying sepia filter to ${image}`);
}
}

class NegativeStrategy implements FilterStrategy {
apply(image: string): void {
console.log(`Applying negative filter to ${image}`);
}
}

Avoid Conditional Statements

This pattern helps avoid conditional statements to select desired behavior. Without using the Strategy pattern, we might have had a method in our ImageProcessor class with a conditional statement to decide which filter to apply. However, by using the Strategy pattern, we're able to avoid this.

class ImageProcessor {
private strategy: FilterStrategy;

constructor(strategy: FilterStrategy) {
this.strategy = strategy;
}

setFilterStrategy(strategy: FilterStrategy): void {
this.strategy = strategy;
}

applyFilter(image: string): void {
this.strategy.apply(image);
}
}

These benefits lead to code that is more maintainable, easier to read, and flexible in the face of changing requirements.

TypeScript Course Instructor Image
TypeScript Course Instructor Image

Time To Transition From JavaScript To TypeScript

Level Up Your TypeScript And Object Oriented Programming Skills. The only complete TypeScript course on the marketplace you building TypeScript apps like a PRO.

SEE COURSE DETAILS

What Can You Do Next 🙏😊

If you liked the article, consider subscribing to Cloudaffle, my YouTube Channel, where I keep posting in-depth tutorials and all edutainment stuff for ssoftware developers.

YouTube @cloudaffle