Learn how to implement the Strategy Pattern in JavaScript using ES6 classes with practical examples, focusing on a data sorting application.
In this section, we will delve into the Strategy Pattern, a behavioral design pattern that enables selecting an algorithm’s implementation at runtime. This pattern is particularly useful in scenarios where multiple algorithms can be applied to a problem, and the choice of algorithm might change depending on the context. We will explore how to implement this pattern in JavaScript using ES6 classes, focusing on a practical example involving a data sorting application.
The Strategy Pattern is a design pattern that defines a family of algorithms, encapsulates each one, and makes them interchangeable. This pattern lets the algorithm vary independently from clients that use it. In JavaScript, due to its dynamic nature and support for first-class functions, implementing the Strategy Pattern can be both intuitive and flexible.
SortStrategy
interface in our example will define a sort
method.BubbleSortStrategy
and QuickSortStrategy
.Sorter
class.Let’s walk through the implementation of the Strategy Pattern in JavaScript using a data sorting application as our example scenario. This application will allow users to choose between different sorting algorithms at runtime.
JavaScript does not have interfaces in the traditional sense, but we can define a base class with a method that throws an error if not implemented. This acts as our interface.
class SortStrategy {
sort(data) {
throw new Error("Method 'sort()' must be implemented.");
}
}
This SortStrategy
class serves as a blueprint for all concrete strategy classes, ensuring they implement the sort
method.
Each concrete strategy will implement the sort
method, providing a different sorting algorithm.
Bubble Sort Strategy:
class BubbleSortStrategy extends SortStrategy {
sort(data) {
console.log("Sorting using Bubble Sort");
let n = data.length;
for (let i = 0; i < n - 1; i++) {
for (let j = 0; j < n - i - 1; j++) {
if (data[j] > data[j + 1]) {
// Swap data[j] and data[j+1]
[data[j], data[j + 1]] = [data[j + 1], data[j]];
}
}
}
return data;
}
}
Quick Sort Strategy:
class QuickSortStrategy extends SortStrategy {
sort(data) {
console.log("Sorting using Quick Sort");
return this.quickSort(data, 0, data.length - 1);
}
quickSort(arr, low, high) {
if (low < high) {
let pi = this.partition(arr, low, high);
this.quickSort(arr, low, pi - 1);
this.quickSort(arr, pi + 1, high);
}
return arr;
}
partition(arr, low, high) {
let pivot = arr[high];
let i = (low - 1);
for (let j = low; j < high; j++) {
if (arr[j] < pivot) {
i++;
[arr[i], arr[j]] = [arr[j], arr[i]];
}
}
[arr[i + 1], arr[high]] = [arr[high], arr[i + 1]];
return i + 1;
}
}
The Sorter
class will use a strategy to perform sorting. It will hold a reference to a SortStrategy
and delegate sorting to it.
class Sorter {
setStrategy(strategy) {
this.strategy = strategy;
}
sort(data) {
return this.strategy.sort(data);
}
}
The client code will allow the user to select a sorting strategy at runtime.
function main() {
const sorter = new Sorter();
const data = [5, 2, 9, 1, 5, 6];
// User chooses sorting strategy
const sortMethod = prompt("Choose sorting method (bubble/quick):");
if (sortMethod === "bubble") {
sorter.setStrategy(new BubbleSortStrategy());
} else if (sortMethod === "quick") {
sorter.setStrategy(new QuickSortStrategy());
} else {
console.log("Invalid sorting method");
return;
}
const sortedData = sorter.sort(data);
console.log(sortedData);
}
main();
SortStrategy
): Acts as a blueprint for concrete strategies. It ensures that each strategy implements the sort
method.BubbleSortStrategy
and QuickSortStrategy
provide specific implementations of the sorting algorithms.Sorter
): Maintains a reference to a SortStrategy
and delegates the sorting task to it. This allows the sorting algorithm to be changed at runtime without modifying the Sorter
class.Sorter
class is decoupled from the concrete strategy implementations, promoting flexibility and maintainability.To better understand the relationships between the components, here is a class diagram illustrating the Strategy Pattern in our sorting application:
classDiagram class Sorter { +setStrategy(SortStrategy) +sort(data) } class SortStrategy { +sort(data) } class BubbleSortStrategy { +sort(data) } class QuickSortStrategy { +sort(data) } SortStrategy <|-- BubbleSortStrategy SortStrategy <|-- QuickSortStrategy Sorter o-- SortStrategy
Sorter
class remains clean and focused on its primary responsibility, delegating the sorting logic to the strategy objects.The Strategy Pattern is a powerful tool for designing flexible and maintainable software. By encapsulating algorithms within strategy classes, you can easily switch between different implementations without altering the context class. In JavaScript, this pattern is particularly effective due to the language’s dynamic capabilities.
By implementing the Strategy Pattern in a data sorting application, we’ve demonstrated how to apply this pattern to solve real-world problems. This approach not only improves code organization but also enhances the application’s ability to adapt to changing requirements.
For further exploration, consider experimenting with additional sorting algorithms or applying the Strategy Pattern to other domains, such as payment processing or file compression.