Module 26: TypeScript Design Patterns
Learn essential design patterns implemented with TypeScript's type system for building robust, maintainable applications.
1. Singleton Pattern
class Database {
private static instance: Database;
private constructor() {}
static getInstance(): Database {
if (!Database.instance) {
Database.instance = new Database();
}
return Database.instance;
}
query(sql: string): void {
console.log(`Executing: ${sql}`);
}
}
const db1 = Database.getInstance();
const db2 = Database.getInstance();
console.log(db1 === db2); // true
2. Factory Pattern
interface Product {
operation(): string;
}
class ConcreteProductA implements Product {
operation(): string {
return "Product A";
}
}
class ConcreteProductB implements Product {
operation(): string {
return "Product B";
}
}
class ProductFactory {
static createProduct(type: "A" | "B"): Product {
switch (type) {
case "A":
return new ConcreteProductA();
case "B":
return new ConcreteProductB();
}
}
}
const product = ProductFactory.createProduct("A");
3. Builder Pattern
interface User {
name: string;
email: string;
age?: number;
address?: string;
}
class UserBuilder {
private user: Partial<User> = {};
setName(name: string): this {
this.user.name = name;
return this;
}
setEmail(email: string): this {
this.user.email = email;
return this;
}
setAge(age: number): this {
this.user.age = age;
return this;
}
setAddress(address: string): this {
this.user.address = address;
return this;
}
build(): User {
if (!this.user.name || !this.user.email) {
throw new Error("Name and email are required");
}
return this.user as User;
}
}
const user = new UserBuilder()
.setName("Alice")
.setEmail("alice@example.com")
.setAge(25)
.build();
4. Observer Pattern
interface Observer<T> {
update(data: T): void;
}
class Subject<T> {
private observers: Observer<T>[] = [];
attach(observer: Observer<T>): void {
this.observers.push(observer);
}
detach(observer: Observer<T>): void {
const index = this.observers.indexOf(observer);
if (index > -1) {
this.observers.splice(index, 1);
}
}
notify(data: T): void {
for (const observer of this.observers) {
observer.update(data);
}
}
}
class EmailObserver implements Observer<string> {
update(message: string): void {
console.log(`Email: ${message}`);
}
}
const subject = new Subject<string>();
const emailObserver = new EmailObserver();
subject.attach(emailObserver);
subject.notify("New notification!");
5. Strategy Pattern
interface PaymentStrategy {
pay(amount: number): void;
}
class CreditCardPayment implements PaymentStrategy {
pay(amount: number): void {
console.log(`Paid ${amount} using Credit Card`);
}
}
class PayPalPayment implements PaymentStrategy {
pay(amount: number): void {
console.log(`Paid ${amount} using PayPal`);
}
}
class PaymentContext {
constructor(private strategy: PaymentStrategy) {}
setStrategy(strategy: PaymentStrategy): void {
this.strategy = strategy;
}
executePayment(amount: number): void {
this.strategy.pay(amount);
}
}
const context = new PaymentContext(new CreditCardPayment());
context.executePayment(100);
context.setStrategy(new PayPalPayment());
context.executePayment(50);
6. Decorator Pattern
interface Component {
operation(): string;
}
class ConcreteComponent implements Component {
operation(): string {
return "Base Component";
}
}
class Decorator implements Component {
constructor(protected component: Component) {}
operation(): string {
return this.component.operation();
}
}
class ConcreteDecoratorA extends Decorator {
operation(): string {
return `DecoratorA(${super.operation()})`;
}
}
class ConcreteDecoratorB extends Decorator {
operation(): string {
return `DecoratorB(${super.operation()})`;
}
}
let component: Component = new ConcreteComponent();
component = new ConcreteDecoratorA(component);
component = new ConcreteDecoratorB(component);
console.log(component.operation());
// Output: DecoratorB(DecoratorA(Base Component))
7. Repository Pattern
interface Repository<T> {
find(id: number): Promise<T | null>;
findAll(): Promise<T[]>;
save(entity: T): Promise<T>;
delete(id: number): Promise<void>;
}
interface User {
id: number;
name: string;
email: string;
}
class UserRepository implements Repository<User> {
private users: User[] = [];
async find(id: number): Promise<User | null> {
return this.users.find(u => u.id === id) || null;
}
async findAll(): Promise<User[]> {
return this.users;
}
async save(user: User): Promise<User> {
this.users.push(user);
return user;
}
async delete(id: number): Promise<void> {
this.users = this.users.filter(u => u.id !== id);
}
}
8. Adapter Pattern
interface ModernInterface {
request(): string;
}
class LegacySystem {
specificRequest(): string {
return "Legacy data";
}
}
class Adapter implements ModernInterface {
constructor(private legacy: LegacySystem) {}
request(): string {
return this.legacy.specificRequest();
}
}
const legacy = new LegacySystem();
const adapter = new Adapter(legacy);
console.log(adapter.request());
9. Command Pattern
interface Command {
execute(): void;
undo(): void;
}
class Light {
turnOn(): void {
console.log("Light is ON");
}
turnOff(): void {
console.log("Light is OFF");
}
}
class LightOnCommand implements Command {
constructor(private light: Light) {}
execute(): void {
this.light.turnOn();
}
undo(): void {
this.light.turnOff();
}
}
class RemoteControl {
private history: Command[] = [];
executeCommand(command: Command): void {
command.execute();
this.history.push(command);
}
undo(): void {
const command = this.history.pop();
if (command) {
command.undo();
}
}
}
10. Dependency Injection
interface Logger {
log(message: string): void;
}
class ConsoleLogger implements Logger {
log(message: string): void {
console.log(message);
}
}
class UserService {
constructor(private logger: Logger) {}
createUser(name: string): void {
this.logger.log(`Creating user: ${name}`);
// Create user logic
}
}
const logger = new ConsoleLogger();
const userService = new UserService(logger);
userService.createUser("Alice");
Key Takeaways
✅ Singleton for single instance
✅ Factory for object creation
✅ Builder for complex construction
✅ Observer for event handling
✅ Strategy for interchangeable algorithms
✅ Repository for data access
Practice Exercises
Exercise 1: Implement State Pattern
Create a state machine for an order system.
Exercise 2: Chain of Responsibility
Build a logging system with multiple handlers.
Next Steps
In Module 27, we'll explore Performance Optimization techniques for TypeScript applications.