Design Patterns in Angular: Strategy Design Pattern
The Strategy Design Pattern is a behavioral design pattern that allows you to define a family of algorithms, encapsulate each one, and make them interchangeable. The key idea is that the client can choose which algorithm (or strategy) to use at runtime. This is particularly useful in situations where you need to change the behavior of an object dynamically based on some condition.
In Angular, implementing the Strategy Pattern can help adhere to the SOLID principles, especially the Open/Closed Principle (open for extension, closed for modification) and the Dependency Inversion Principle.
Steps to Implement Strategy Design Pattern in Angular
- Define the Strategy Interface: Define a common interface for the strategies that will be interchangeable.
- Create Concrete Strategies: Implement concrete strategies that perform different behaviors.
- Context Class: A class that uses a reference to a strategy and delegates work to the strategy.
- Dependency Injection: Leverage Angular’s Dependency Injection (DI) to inject different strategies into a component or service.
Example: Payment System with Strategy Pattern
Create a Payment system where a user can select a payment strategy (Credit Card, PayPal, Cash). The payment system will use the Strategy Pattern to handle the different payment methods without modifying the core payment logic
Step 1: Define the Strategy Interface
export interface PaymentStrategy {
pay(amount: number): string;
}
Step 2: Implement Concrete Strategies
import { PaymentStrategy } from './payment-strategy.interface';
export class CreditCardPayment implements PaymentStrategy {
pay(amount: number): string {
return `Paid ${amount} using Credit Card.`;
}
}
import { PaymentStrategy } from './payment-strategy.interface';
export class PayPalPayment implements PaymentStrategy {
pay(amount: number): string {
return `Paid ${amount} using PayPal.`;
}
}
import { PaymentStrategy } from './payment-strategy.interface';
export class CashPayment implements PaymentStrategy {
pay(amount: number): string {
return `Paid ${amount} using Cash.`;
}
}
Step 3: Create the Context Class
This class will use the strategy and delegate the payment operation
import { Injectable } from '@angular/core';
import { PaymentStrategy } from './payment-strategy.interface';
import { CreditCardPayment } from './credit-card-payment.strategy';
@Injectable({
providedIn: 'root'
})
export class PaymentContextService {
private paymentStrategy: PaymentStrategy;
constructor() {
//Default value 😊
this.paymentStrategy = new CreditCardPayment();
}
setPaymentStrategy(strategy: PaymentStrategy) {
this.paymentStrategy = strategy;
}
executePayment(amount: number): string {
return this.paymentStrategy.pay(amount);
}
}
Step 4: Use Dependency Injection in Angular Component
Now, use the PaymentContextService
in an Angular component and dynamically switch payment strategies.
import { Component } from '@angular/core';
import { PaymentContextService } from './services/payment-context.service';
import { CreditCardPayment } from './services/credit-card-payment.strategy';
import { PayPalPayment } from './services/paypal-payment.strategy';
import { CashPayment } from './services/bitcoin-payment.strategy';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
constructor(private paymentContext: PaymentContextService) {}
makePayment(amount: number, paymentMethod: string) {
switch (paymentMethod) {
case 'credit':
this.paymentContext.setPaymentStrategy(new CreditCardPayment());
break;
case 'paypal':
this.paymentContext.setPaymentStrategy(new PayPalPayment());
break;
case 'bitcoin':
this.paymentContext.setPaymentStrategy(new CashPayment());
break;
default:
console.log('Invalid payment method');
return;
}
const result = this.paymentContext.executePayment(amount);
console.log(result);
}
}
Explanation
- Strategy Interface (
PaymentStrategy
): This is the contract that all payment strategies must adhere to. Each concrete strategy (Credit Card, PayPal, Cash) will implement thepay
method defined in the interface. - Concrete Strategies (
CreditCardPayment
,PayPalPayment
,CashPayment
): Each of these classes implements thePaymentStrategy
interface and provides its own logic for handling payments. - PaymentContextService: This service is the “context” that holds a reference to the current strategy. It allows the payment strategy to be swapped at runtime through the
setPaymentStrategy()
method. TheexecutePayment()
method delegates the payment operation to the selected strategy. - Component (
AppComponent
): The Angular component uses thePaymentContextService
to execute payments. It can switch between different strategies dynamically by callingsetPaymentStrategy()
based on user input (e.g., button clicks).
Benefits of this Implementation:
- Adheres to SOLID Principles:
- Single Responsibility Principle: Each class has a single responsibility. For example,
CreditCardPayment
handles credit card payments,PayPalPayment
handles PayPal payments, etc. - Open/Closed Principle: The payment system is open for extension but closed for modification. You can add new payment methods without modifying existing code by simply creating a new strategy.
- Dependency Inversion Principle: High-level modules (e.g.,
PaymentContextService
) depend on the abstraction (PaymentStrategy
), not on low-level implementations likeCreditCardPayment
,PayPalPayment
, etc.
- Flexibility: The payment strategy can be easily swapped based on user input, enabling flexible and maintainable code.