This content originally appeared on DEV Community and was authored by Mohamed Achaq
Hello guys and welcome to a new blog post about Design Patterns in TypeScript.
Design Patterns
First what are design patterns?
Design Patterns are a set of best practices that are used to solve common problems in software development
and to make it easier to understand and maintain code.
There are several design patterns such as Singleton, Factory, Observer, Command, Strategy, Template Method, Builder,
Decorator, Adapter, Facade, Proxy, and many more ... and I will cover the most important ones in this blog post.
and as you've read before design patterns are a powerful tool to help you to write better code and slove
problems faster and 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 and you can use the singleton module.
class Singleton {
private static instance: Singleton;
private constructor() {}
public static getInstance(): Singleton {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}
return Singleton.instance;
}
}
and you can use it like this:
const singleton = Singleton.getInstance();
Factory
The Factory pattern is a design pattern that lets you create objects without specifying the exact class of the object that will be created.
In this example we want to make car depending on it's type so instad of making a class for each type we make a single factory class to make us a car 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`);
}
}
}
and then you can use it like this:
const factory = new VehicleFactory();
const car = factory.createVehicle('car');
const truck = factory.createVehicle('truck');
Observer
The Observer pattern is a design pattern that lets you define a subscription mechanism to notify multiple objects and it's used in
mainly in the event driven programming paradigm.
Implementing the Observer pattern in Typescript is very easy and you can use the observer module.
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));
}
}
then you can use it like this:
class Observer {
public update(data: any) {
console.log(data);
}
}
and then you can let the subject know that there is a new data available:
const subject = new Subject();
const observer = new Observer();
subject.subscribe(observer);
subject.notify('Hello World');
and you can also unsubscribe the observer from the subject:
subject.unsubscribe(observer);
Command
The Command pattern is a design pattern that lets you encapsulate all information needed to perform an action in one object .
Implementing the Command pattern in Typescript is very easy and you can use the command module.
class Command {
constructor(private receiver: Receiver) {}
public execute() {
this.receiver.action();
}
}
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();
Strategy
The Strategy pattern is a design pattern that 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 use the strategy module.
class Strategy {
public LastElement(data: []) {
return data[data.length - 1];
}
}
and then you can use it like this:
const strategy = new Strategy();
const data = [1, 2, 3, 4, 5];
let last = strategy.LastElement(data);
Template Method
The Template Method pattern is a design pattern that 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');
}
}
Builder
The Builder pattern is a design pattern that lets you construct complex objects step by step and it's used in
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();
}
}
Then you make a product class :
class Product {
constructor(private partA: string, private partB: string) {}
}
Then you make a director class to build the product:
class Director {
public build(builder: Builder) {
builder.buildPartA();
builder.buildPartB();
}
}
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('', '');
}
}
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();
Decorator
The Decorator pattern is a design pattern that 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...');
}
}
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();
}
}
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...');
}
}
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...');
}
}
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...');
}
}
Then you make your pizza:
const pizza = new CheeseDecorator(new HamDecorator(new MushroomDecorator(new Pizza())));
pizza.makePizza();
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 that 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;
}
}
Then you make a plug class:
class Plug {
constructor(private type: string) {}
public getType() {
return this.type;
}
}
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();
}
}
Then you make your plug:
const plug = new SocketAdapter(new Socket('Type-C'));
console.log(plug.getType());
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 that 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...');
}
}
Then you make a facade class that will make the car with an engine, transmission, and wheels:
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...');
}
}
Then you make your car:
const car = new CarFacade(new Car());
car.makeCar();
Proxy
The Proxy design pattern is a design pattern that 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...');
}
}
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();
}
}
Then you make your library:
const library = new LibraryProxy(new Library());
library.getBooks();
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.
This content originally appeared on DEV Community and was authored by Mohamed Achaq
Mohamed Achaq | Sciencx (2022-05-01T23:41:51+00:00) Design Patterns in Typescript. Retrieved from https://www.scien.cx/2022/05/01/design-patterns-in-typescript-2/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.