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.
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 DETAILSWhat 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.