BehavioralTypeScriptverifiedVerified
Strategy Pattern in TypeScript
Defines a family of algorithms, encapsulates each one, and makes them interchangeable so the algorithm can vary independently from the clients that use it.
How to Implement the Strategy Pattern in TypeScript
1Step 1: Define the Strategy interface
interface SortStrategy<T> {
sort(data: T[]): T[];
}2Step 2: Implement concrete strategies
class BubbleSort<T> implements SortStrategy<T> {
sort(data: T[]): T[] {
const arr = [...data];
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < arr.length - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
}
}
}
return arr;
}
}
class QuickSort<T> implements SortStrategy<T> {
sort(data: T[]): T[] {
if (data.length <= 1) return data;
const pivot = data[Math.floor(data.length / 2)];
const left = data.filter(x => x < pivot);
const mid = data.filter(x => x === pivot);
const right = data.filter(x => x > pivot);
return [...this.sort(left), ...mid, ...this.sort(right)];
}
}3Step 3: Create the Context class that uses a strategy
class Sorter<T> {
constructor(private strategy: SortStrategy<T>) {}
setStrategy(strategy: SortStrategy<T>): void {
this.strategy = strategy;
}
sort(data: T[]): T[] {
return this.strategy.sort(data);
}
}4Step 4: Swap strategies at runtime
// Usage
const sorter = new Sorter(new BubbleSort<number>());
console.log(sorter.sort([5, 3, 1, 4, 2])); // [1, 2, 3, 4, 5]
sorter.setStrategy(new QuickSort<number>());
console.log(sorter.sort([5, 3, 1, 4, 2])); // [1, 2, 3, 4, 5]// ── Payment Processing with Strategy Pattern ──────────────────────
interface PaymentDetails {
amount: number;
currency: string;
description: string;
}
interface PaymentResult {
success: boolean;
transactionId: string;
timestamp: number;
provider: string;
errorMessage?: string;
}
interface PaymentStrategy {
readonly name: string;
validate(details: PaymentDetails): string[];
process(details: PaymentDetails): Promise<PaymentResult>;
}
// ── Concrete Strategies ───────────────────────────────────────────
class StripeStrategy implements PaymentStrategy {
readonly name = "stripe";
constructor(private apiKey: string) {}
validate(details: PaymentDetails): string[] {
const errors: string[] = [];
if (details.amount <= 0) errors.push("Amount must be positive");
if (!["USD", "EUR", "GBP"].includes(details.currency)) {
errors.push(`Currency ${details.currency} not supported by Stripe`);
}
return errors;
}
async process(details: PaymentDetails): Promise<PaymentResult> {
// Simulate Stripe API call
return {
success: true,
transactionId: `stripe_${crypto.randomUUID()}`,
timestamp: Date.now(),
provider: this.name,
};
}
}
class PayPalStrategy implements PaymentStrategy {
readonly name = "paypal";
validate(details: PaymentDetails): string[] {
const errors: string[] = [];
if (details.amount < 1) errors.push("PayPal minimum is $1.00");
return errors;
}
async process(details: PaymentDetails): Promise<PaymentResult> {
return {
success: true,
transactionId: `pp_${crypto.randomUUID()}`,
timestamp: Date.now(),
provider: this.name,
};
}
}
// ── Payment Processor (Context) ───────────────────────────────────
class PaymentProcessor {
private strategy: PaymentStrategy;
constructor(strategy: PaymentStrategy) {
this.strategy = strategy;
}
setStrategy(strategy: PaymentStrategy): void {
this.strategy = strategy;
}
async charge(details: PaymentDetails): Promise<PaymentResult> {
const errors = this.strategy.validate(details);
if (errors.length > 0) {
return {
success: false,
transactionId: "",
timestamp: Date.now(),
provider: this.strategy.name,
errorMessage: errors.join("; "),
};
}
return this.strategy.process(details);
}
}
export { PaymentProcessor, StripeStrategy, PayPalStrategy, type PaymentStrategy, type PaymentResult };Strategy Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Strategy Pattern in the Real World
“Consider a GPS app offering route options: fastest, shortest, or avoid tolls. The destination is the same, but the navigation algorithm changes based on your preference. The app (context) simply hands the journey off to whichever routing strategy you selected; you can switch strategies mid-trip without the app needing to change its structure.”