DEV Community

Cover image for Typescript Design Patterns
Mohamed Achaq
Mohamed Achaq

Posted on • Updated on

 

Typescript Design Patterns

Hello guys and welcome to a new blog post about Design Patterns in TypeScript.

What is a pattern

A Design Pattern is a set of best practices used to solve common problems in software development and make writing clean and mantainable code easier.

Singleton

The Singleton pattern is a design pattern that restricts the instantiation of a class to one object and it's
used to ensure that only one object of a class is created.

Implementing the Singleton pattern in Typescript is very easy

class Singleton {
  private static instance: Singleton;

  private constructor() {}

  public static getInstance(): Singleton {
    if (!Singleton.instance) {
      Singleton.instance = new Singleton();
    }
    return Singleton.instance;
  }
}
Enter fullscreen mode Exit fullscreen mode

and you can use it like this:

const singleton = Singleton.getInstance();
Enter fullscreen mode Exit fullscreen mode

Factory

The Factory pattern is a design pattern lets you create objects without specifying the exact class of the object that will be created.

In this example we want to make a vehicle depending on it's type so instad of making a class for each type we make a single factory class to make us a vehicle depending on the type we give it .

class VehicleFactory {
  public createVehicle(type: string): Vehicle {
    switch (type) {
      case 'car':
        return new Car();
      case 'truck':
        return new Truck();
      default:
        throw new Error(`Vehicle of type ${type} not found`);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

and then you can use it like this to make as much as you want of vehicles as long as you provide the type

const factory = new VehicleFactory();
const car = factory.createVehicle('car');
const truck = factory.createVehicle('truck');
Enter fullscreen mode Exit fullscreen mode

Observer

The Observer pattern is a design pattern lets you define a subscription mechanism to notify multiple objects and it's used in the event driven programming paradigm.

Implementing the Observer pattern in Typescript can look like this

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

  public subscribe(observer: Observer) {
    this.observers.push(observer);
  }

  public unsubscribe(observer: Observer) {
    const index = this.observers.indexOf(observer);
    this.observers.splice(index, 1);
  }

  public notify(data: any) {
    this.observers.forEach(observer => observer.update(data));
  }
}
Enter fullscreen mode Exit fullscreen mode

then you will need an observer class

class Observer {
  public update(data: any) {
    console.log(data);
  }
}
Enter fullscreen mode Exit fullscreen mode

and then you can let the subject know that there is a new data available by subscribing to the observer we created

const subject = new Subject();
const observer = new Observer();
subject.subscribe(observer);
subject.notify('Hello World');
Enter fullscreen mode Exit fullscreen mode

and you can also unsubscribe the observer from the subject:

subject.unsubscribe(observer);
Enter fullscreen mode Exit fullscreen mode

Command

The Command pattern is a design pattern lets you encapsulate all information needed to perform an action in one object .

Implementing the Command pattern can look like this

class Command {
  constructor(private receiver: Receiver) {}

  public execute() {
    this.receiver.action();
  }
}
Enter fullscreen mode Exit fullscreen mode

then you can use the command module to create a command object and pass it to the invoker.

const receiver = new Receiver();
const command = new Command(receiver);
const invoker = new Invoker();
invoker.setCommand(command);
invoker.execute();
Enter fullscreen mode Exit fullscreen mode

Strategy

The Strategy pattern is a design pattern lets you define a family of algorithms, encapsulate each one, and make them interchangeable.

Implementing the Strategy pattern in Typescript is very easy and you can start with this Strategy class

class Strategy {
  public LastElement(data: []) {
    return data[data.length - 1];
  }

}
Enter fullscreen mode Exit fullscreen mode

and then you can use it like this:

const strategy = new Strategy();
const data = [1, 2, 3, 4, 5];

let last = strategy.LastElement(data);
Enter fullscreen mode Exit fullscreen mode

Template Method

The Template Method pattern is a design pattern lets you define the skeleton of an algorithm in an operation, deferring some steps to subclasses.

For example you want to make a pizza and you want to make it with tomato sauce, cheese and ham but you don't want to repeat the same steps for every pizza you make
so instad you can define the steps in a template method and then you can use it to make different pizzas.

The Implementation will be like this

class Pizza {
  public makePizza() {
    this.prepareDough();
    this.addSauce();
    this.addToppings();
    this.bake();
  }

  public prepareDough() {
    console.log('Preparing dough...');
  }

  public addSauce() {
    console.log('Adding sauce...');
  }

  public addToppings() {
    console.log('Adding toppings: cheese, ham, mushrooms');
  }

  public bake() {
    console.log('Bake for 25 minutes at 350');
  }
}
Enter fullscreen mode Exit fullscreen mode

Builder

The Builder pattern is a design pattern lets you construct complex objects step by step and it's used mainly in the object oriented programming paradigm.

And you can implement the Builder pattern in Typescript is very easy and you can use the builder module.

class Builder {
  public build() {
    return new Product();
  }
}
Enter fullscreen mode Exit fullscreen mode

Then you make a product class :

class Product {
  constructor(private partA: string, private partB: string) {}
}
Enter fullscreen mode Exit fullscreen mode

Then you make a director class to build the builder:

class Director {
  public build(builder: Builder) {
    builder.buildPartA();
    builder.buildPartB();
  }
}
Enter fullscreen mode Exit fullscreen mode

Then you make a ConcreteBuilder that implements the Builder interface:

class ConcreteBuilder extends Builder {
  private product: Product;

  public buildPartA() {
    this.product.partA = 'Part A';
  }

  public buildPartB() {
    this.product.partB = 'Part B';
  }

  public getProduct() {
    const product = this.product;
    this.reset();
    return product;
  }

  public reset() {
    this.product = new Product('', '');
  }
}
Enter fullscreen mode Exit fullscreen mode
const builder = new ConcreteBuilder();
const director = new Director();
director.build(builder);
// make a new product
const product = new Product('Part A', 'Part B');
// get the product
const newProduct = builder.getProduct();
Enter fullscreen mode Exit fullscreen mode

Decorator

The Decorator pattern is a design pattern lets you dynamically change the behavior of an object at run time
and it's communly used in frameworks like Angular.

And you can implement the Decorator pattern in Typescript is very easy and you can use the decorator module.

Again imagine you want to make a pizza and you want to make it with tomato sauce, cheese and ham but you don't want to repeat the same steps for every pizza you make

First you need to make a pizza class:

class Pizza {
  public makePizza() {
    console.log('Making a pizza...');
  }
}
Enter fullscreen mode Exit fullscreen mode

Then you make a decorator class that will decorate the pizza class:

class PizzaDecorator extends Pizza {
  constructor(public pizza: Pizza) {
    super();
  }

  public makePizza() {
    this.pizza.makePizza();
  }
}
Enter fullscreen mode Exit fullscreen mode

Then you make a concrete decorator class that extends the decorator class to add the cheese:

class CheeseDecorator extends PizzaDecorator {
  constructor(pizza: Pizza) {
    super(pizza);
  }

  public makePizza() {
    super.makePizza();
    console.log('Adding cheese...');
  }
}
Enter fullscreen mode Exit fullscreen mode

Then you make a concrete decorator class that extends the decorator class to add the ham:

class HamDecorator extends PizzaDecorator {
  constructor(pizza: Pizza) {
    super(pizza);
  }

  public makePizza() {
    super.makePizza();
    console.log('Adding ham...');
  }
}
Enter fullscreen mode Exit fullscreen mode

Then you make a concrete decorator class that extends the decorator class to add the mushrooms:

class MushroomDecorator extends PizzaDecorator {
  constructor(pizza: Pizza) {
    super(pizza);
  }

  public makePizza() {
    super.makePizza();
    console.log('Adding mushrooms...');
  }
}
Enter fullscreen mode Exit fullscreen mode

Then you make your pizza:

const pizza = new CheeseDecorator(new HamDecorator(new MushroomDecorator(new Pizza())));
pizza.makePizza();
Enter fullscreen mode Exit fullscreen mode

as you can see the decorator pattern uses nested classes and inheritance to add new functionality to an object.

Adapter

The Adapter design pattern is a design pattern lets you convert the interface of a class into another interface
that it expects.

imagine you want to turn a socket into a plug.

class Socket {
  constructor(private type: string) {}

  public getType() {
    return this.type;
  }
}
Enter fullscreen mode Exit fullscreen mode

Then you make a plug class:

class Plug {
  constructor(private type: string) {}

  public getType() {
    return this.type;
  }
}
Enter fullscreen mode Exit fullscreen mode

Then you make an adapter class that will adapt the socket class to the plug class:

class SocketAdapter implements Plug {
  constructor(private socket: Socket) {}

  public getType() {
    return this.socket.getType();
  }
}
Enter fullscreen mode Exit fullscreen mode

Then you make your plug:

const plug = new SocketAdapter(new Socket('Type-C'));
console.log(plug.getType());
Enter fullscreen mode Exit fullscreen mode

As you can see the adapter class uses inheritance to adapt the socket class to the plug class.

Facade

The Facade pattern is a design pattern lets you define a simple unified interface to a large body of code .

imagine you want to make a car and you want to make it with a engine, transmission, and wheels.

First you need to make a car class:

class Car {
  public makeCar() {
    console.log('Making a car...');
  }
}
Enter fullscreen mode Exit fullscreen mode

Then you make a facade class that will make the car with an engine, transmission, and wheels and abstract the process from the user

class CarFacade {
  constructor(private car: Car) {}

  public makeCar() {
    this.car.makeCar();
    this.makeEngine();
    this.makeTransmission();
    this.makeWheels();
  }

  private makeEngine() {
    console.log('Making engine...');
  }

  private makeTransmission() {
    console.log('Making transmission...');
  }

  private makeWheels() {
    console.log('Making wheels...');
  }
}
Enter fullscreen mode Exit fullscreen mode

Then you make your car:

const car = new CarFacade(new Car());
car.makeCar();
Enter fullscreen mode Exit fullscreen mode

Proxy

The Proxy design pattern is a design pattern lets you provide a surrogate or placeholder object
for another object to control access to it.

For example imagine you want to give students access to a library but you don't want them to be able to access the library directly.

First you need to make a library class:

class Library {
  public getBooks() {
    console.log('Getting books...');
  }
}
Enter fullscreen mode Exit fullscreen mode

Then you make a proxy class that will give students access to the library:

class LibraryProxy {
  constructor(private library: Library) {}

  public getBooks() {
    this.library.getBooks();
  }
}
Enter fullscreen mode Exit fullscreen mode

Then you make your library:

const library = new LibraryProxy(new Library());
library.getBooks();
Enter fullscreen mode Exit fullscreen mode

Conclusion

You don't need to know design patterns to make software but you can get lot of benifits from them,
like writing clean and maintanable code and overcome problems faster, better and more efficiently.
That was it for this blog post about design patterns, hope you enjoy it.

Top comments (5)

Collapse
 
billsourour profile image
Bill Sourour

Thank you for taking the time to put together some examples in TypeScript.

For readers interested in learning more, these patterns are part of what is often called the "Gang of Four" patterns and they come from a book called Design Patterns: Elements of Reusable Object-Oriented Software (1994) written by the "Gang of Four"; Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides.

It's important to not only know the patterns but also to understand what problems they solve so that you can make good decisions about which patterns to use when.

Collapse
 
triyanox profile image
Mohamed Achaq

Thank you so much and I really recommend this book too to anyone wants to dive deeper and understand design patterns !

Collapse
 
andresbecker profile image
Andres Becker

Thanks!

Collapse
 
andrewbaisden profile image
Andrew Baisden

Great read thanks for putting this together.

Collapse
 
triyanox profile image
Mohamed Achaq

Thank you so much really appreciated !

An Animated Guide to Node.js Event Loop

>> Check out this classic DEV post <<