DEV Community

Cover image for Advanced Design Patterns in TypeScript
Shafayet Hossain
Shafayet Hossain

Posted on

Advanced Design Patterns in TypeScript

Design patterns are time tested solutions to recurring problems in software design. They enhance code readability, scalability, and maintainability. TypeScript, with its strong typing and modern JavaScript foundation, is an excellent language to implement these patterns effectively.

This article delves into advanced and commonly used design patterns, explaining their concepts, TypeScript implementations, and practical use cases. Whether you're a seasoned developer or exploring TypeScript, you'll gain valuable insights into building robust applications.

What Are Design Patterns?

Design patterns are reusable solutions to common design problems. They are categorized into three main types:

  1. Creational Patterns: Deal with object creation.
  2. Structural Patterns: Focus on object composition.
  3. Behavioral Patterns: Concerned with object interaction.

Creational Design Patterns in TypeScript

1. Singleton Pattern
Ensures that a class has only one instance throughout the application.

Use Case: Managing global state or configurations.

Implementation:

class Singleton {
  private static instance: Singleton;

  private constructor() {}

  static getInstance(): Singleton {
    if (!Singleton.instance) {
      Singleton.instance = new Singleton();
    }
    return Singleton.instance;
  }

  public showMessage(): void {
    console.log("Hello, Singleton!");
  }
}

// Usage
const singleton1 = Singleton.getInstance();
const singleton2 = Singleton.getInstance();
console.log(singleton1 === singleton2); // true
Enter fullscreen mode Exit fullscreen mode



2. Factory Method
Creates objects without specifying their exact class.

Use Case: When the object creation logic needs to be abstracted.

Implementation:

interface Product {
  operation(): string;
}

class ConcreteProductA implements Product {
  operation(): string {
    return "Product A";
  }
}

class ConcreteProductB implements Product {
  operation(): string {
    return "Product B";
  }
}

abstract class Creator {
  abstract factoryMethod(): Product;

  someOperation(): string {
    const product = this.factoryMethod();
    return `Creator: ${product.operation()}`;
  }
}

class ConcreteCreatorA extends Creator {
  factoryMethod(): Product {
    return new ConcreteProductA();
  }
}

class ConcreteCreatorB extends Creator {
  factoryMethod(): Product {
    return new ConcreteProductB();
  }
}

// Usage
const creatorA = new ConcreteCreatorA();
console.log(creatorA.someOperation());
Enter fullscreen mode Exit fullscreen mode



3. Builder Pattern
Separates object construction from its representation.

Use Case: Construct complex objects step-by-step.

Implementation:

class Product {
  private parts: string[] = [];

  addPart(part: string): void {
    this.parts.push(part);
  }

  listParts(): void {
    console.log(`Product parts: ${this.parts.join(", ")}`);
  }
}

class Builder {
  private product = new Product();

  reset(): void {
    this.product = new Product();
  }

  addPartA(): void {
    this.product.addPart("Part A");
  }

  addPartB(): void {
    this.product.addPart("Part B");
  }

  getProduct(): Product {
    const result = this.product;
    this.reset();
    return result;
  }
}

// Usage
const builder = new Builder();
builder.addPartA();
builder.addPartB();
const product = builder.getProduct();
product.listParts();
Enter fullscreen mode Exit fullscreen mode

Structural Design Patterns in TypeScript

1. Adapter Pattern
Converts the interface of a class into another interface.

Use Case: Integrating third-party libraries.

Implementation:

class OldSystem {
  oldRequest(): string {
    return "Old System";
  }
}

class NewSystem {
  newRequest(): string {
    return "New System";
  }
}

class Adapter extends OldSystem {
  private adaptee: NewSystem;

  constructor(adaptee: NewSystem) {
    super();
    this.adaptee = adaptee;
  }

  oldRequest(): string {
    return this.adaptee.newRequest();
  }
}

// Usage
const adaptee = new NewSystem();
const adapter = new Adapter(adaptee);
console.log(adapter.oldRequest());
Enter fullscreen mode Exit fullscreen mode



2. Composite Pattern
Composes objects into tree structures to represent part-whole hierarchies.

Use Case: Managing hierarchical data.

Implementation:

abstract class Component {
  abstract operation(): string;
}

class Leaf extends Component {
  operation(): string {
    return "Leaf";
  }
}

class Composite extends Component {
  private children: Component[] = [];

  add(component: Component): void {
    this.children.push(component);
  }

  operation(): string {
    const results = this.children.map(child => child.operation());
    return `Composite(${results.join(", ")})`;
  }
}

// Usage
const leaf = new Leaf();
const composite = new Composite();
composite.add(leaf);
console.log(composite.operation());
Enter fullscreen mode Exit fullscreen mode

Behavioral Design Patterns in TypeScript

1. Observer Pattern
Defines a dependency between objects so that one object changes state, all dependents are notified.

Use Case: Event systems.

Implementation:

interface Observer {
  update(message: string): void;
}

class Subject {
  private observers: Observer[] = [];

  attach(observer: Observer): void {
    this.observers.push(observer);
  }

  notify(message: string): void {
    this.observers.forEach(observer => observer.update(message));
  }
}

class ConcreteObserver implements Observer {
  update(message: string): void {
    console.log(`Received message: ${message}`);
  }
}

// Usage
const subject = new Subject();
const observer1 = new ConcreteObserver();
const observer2 = new ConcreteObserver();
subject.attach(observer1);
subject.attach(observer2);
subject.notify("Event occurred!");
Enter fullscreen mode Exit fullscreen mode



2. Strategy Pattern
Defines a family of algorithms and makes them interchangeable.

Use Case: Payment methods or sorting algorithms.

Implementation:

interface Strategy {
  execute(a: number, b: number): number;
}

class AddStrategy implements Strategy {
  execute(a: number, b: number): number {
    return a + b;
  }
}

class MultiplyStrategy implements Strategy {
  execute(a: number, b: number): number {
    return a * b;
  }
}

class Context {
  constructor(private strategy: Strategy) {}

  executeStrategy(a: number, b: number): number {
    return this.strategy.execute(a, b);
  }
}

// Usage
const addContext = new Context(new AddStrategy());
console.log(addContext.executeStrategy(2, 3)); // 5
Enter fullscreen mode Exit fullscreen mode

At the end,,,

Design patterns are powerful tools for building scalable, maintainable applications. TypeScript's robust type system and modern syntax provide an excellent platform for implementing these patterns effectively. By understanding and applying these design patterns, developers can create well architected software solutions that stand the test of time.
Catch you in the next article, lad!!!


My personal website: https://shafayet.zya.me


Horrible setup😭

Image description

Top comments (0)