Design Patterns in Typescript and Node.js
What are design patterns in TypeScript?
Design patterns are best practices implemented by developers. They solve general problems that occurs during the software development phase. These design patterns are invented after several rounds of trial and error over a period of time.
What are the different types of design patterns?
- Creational: The way we create objects in an object-oriented style. it applies patterns in the way we instantiate a class.
- Structural: How our classes and objects are composed to form a larger structure in our application.
- Behavioral: How the objects can interact efficiently without being tightly coupled.
Design Patterns
单例模式 Singleton pattern
The singleton pattern implies that there should be only one instance for a class. In laymen’s terms, there should be only one president for a country at a time. By following this pattern, we can avoid having multiple instances for a particular class.
A good example of singleton pattern is database connection in our application. Having multiple instances of a database in our application makes an application unstable.
import { MongoClient, Db } from "mongodb";
class DBInstance {
private static instantce: Db;
pricate constructor() {}
static getInstance() {
if (!this.instance) {
const URL = "mongodb://localhost:27017";
const dbName = "sample";
MongoClient.connect(URL, (err, client) => {
if(err) console.log("DB Error", err);
const db = client.db(dbName);
this.instance = db;
})
}
return this.instance;
}
}
抽象工厂模式 Abstract Factory Pattern
Start with Simple Facotry Pattern (工厂模式)
To make it simpler, let me give you an analogy. Let’s say that you are hungry and want some food. You can either cook for yourself or you can order from a restaurant. In that second way, you don’t need to learn or know how to cook to eat some food.
Similarly, the factory pattern simply generates an object instance for a user without exposing any instantiation logic to the client.
Extending Simple Facotry example
Extending our simple factory example, let’s say you are hungry and you’ve decided to order food from a restaurant. Based on your preference, you might order a different cuisine. Then, you might need to select the best restaurant based on the cuisine.
As you can see, there is a dependency between your food and the restaurant. Different restaurants are better for different cuisine.
Laptop store with Abstract Factory Pattern
- Example in Github: https://github.com/xiaokatech/lab-nodeJS
- Folder location: 03-design-parttern/02-abstract-factory-pattern
- Folder tree
- class
- LaptopFactory.ts
- MacbookProcessor.ts
- MacbookStorage.ts
- interface
- ILaptopFactory.ts
- IProcessor.ts
- IStorage.ts
- index.ts
- class
import { ILaptopFactory } from "./interface/ILaptopFactory";
import { IProcessor } from "./interface/IProcessor";
export const buildLaptop = (laptopFactory: ILaptopFactory): IProcessor => {
const processor = laptopFactory.createProcessor();
const storage = laptopFactory.createStorage();
processor.attachStorage(storage);
return processor;
};
建造者模式 Builder pattern
The builder pattern allows you to create different flavors of an object without using a constructor in a class.
UserBuilder
- Example in Github: https://github.com/xiaokatech/lab-nodeJS
- Folder location: 03-design-parttern/03-builder-pattern
- Folder tree
- class
- user.ts
- userBuilder.ts
- index.ts
- class
import { UserBuilder } from "./class/userBuilder";
const userBuilder = new UserBuilder();
userBuilder.setFirstName("John");
userBuilder.setLastName("Doe");
userBuilder.setGender("man");
userBuilder.setAge(30);
console.log("getAllValues", userBuilder.getAllValues()); // instance of UserBuilder
console.log("typeof getAllValues", typeof userBuilder.getAllValues()); // object
console.log("build", userBuilder.build()); // instance of User
console.log("typeof build", typeof userBuilder.build()); // object
适配器模式 Adapter Pattern
A classic example of an adapter pattern will be a differently shaped power socket. Sometimes, the socket and device plug doesn’t fit. To make sure it works, we will use an adapter. That’s exactly what we are going to do in the adapter pattern.
It is a process of wrapping the incompatible object in an adapter to make it compatible with another class.
- Example in Github: https://github.com/xiaokatech/lab-nodeJS
- Folder location: 03-design-parttern/04-adapter-pattern
- Folder tree
- class
- CustomerError-with-adapter-pattern.ts
- CustomerError.ts
- NewCustomerError.ts
- interface
- IError.ts
- INewError.ts
- class
import { INewError } from "../interface/INewError";
import { NewCustomerError } from "./NewCustomerError";
export class CustomeError_v2 implements INewError {
message: string;
constructor(message: string) {
this.message = message;
}
serialize() {
// @TODO: In future replace this function with NewCustomerError
return new NewCustomerError(this.message).serialize();
}
}
观察者模式 Observer Pattern
An observer pattern is a way to update the dependents when there is a state change in another object. it usually contains Observer
and Observable
. Observer
subscribes to Observable
and where there is a change, observable notifies the observers.
- Example in Github: https://github.com/xiaokatech/lab-nodeJS
- Folder location: 03-design-parttern/05-observer-pattern
- Folder tree
- class
- Auth.ts
- Follwer.ts
- Tweet.ts
- interface
- IOberserver.ts
- IObeservable.ts
- index.ts
- class
import express, { Application, Request, Response } from "express";
// import DBInstance from './helper/DB'
import bodyParser from "body-parser";
import { Author } from "./class/Auth";
import { Follower } from "./class/Follower";
import { Tweet } from "./class/Tweet";
const app = express();
async function start() {
try {
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// const db = await DBInstance.getInstance()
app.post("/activate", async (req: Request, res: Response) => {
try {
const follower1 = new Follower("Ganesh");
const follower2 = new Follower("Doe");
const author = new Author();
author.subscribe(follower1);
author.subscribe(follower2);
author.sendTweet(new Tweet("Welcome", "Bruce Lee"));
res.status(200).json({ success: true, data: null });
} catch (e) {
console.log(e);
res.status(500).json({ success: false, data: null });
}
});
app.listen(4000, () => {
console.log("Server is running on PORT 4000");
});
} catch (e) {
console.log("Error while starting the server", e);
}
}
start();
```
## Resources
- Understanding design patterns in TypeScript and Node.js: https://blog.logrocket.com/understanding-design-patterns-typescript-node-js/
策略模式 Strategy Pattern
The strategy pattern allows you to select an algorithm or strategy at runtime. The real use case for this scenario would be switching file storage strategy based on the file size.
Consider that you want to handle file storage based on file size in your application:
- Example in Github: https://github.com/xiaokatech/lab-nodeJS
- Folder location: 03-design-parttern/06-strategy-pattern
- Folder tree
- class
- AWSWriterWrapper.ts
- DiskWriter.ts
- Writer.ts
- interface
- IFileWriter.ts
- index.ts
- class
import { AWSWriterWrapper } from "./class/AWSWriterWrapper";
import { DiskWriter } from "./class/DiskWriter";
import { Writer } from "./class/Writer";
let size = 1000;
if (size < 1000) {
const writer = new Writer(new DiskWriter());
writer.write("file path coms here");
} else {
const writer = new Writer(new AWSWriterWrapper());
writer.write(undefined);
}
责任链模式 Chain of responsibility Pattern
The chain of responsibility allows an object to go through a chain of conditions or functionalities. Instead of managing all the functionalities and conditions in one place, it splits into chains of conditions that an object must pass through.
See express middleware: link
外观模式 Facade Pattern @TODO
状态模式 State Pattern @TODO
Anti-patterns in Typescript @TODO
Overusing the any
type
Class complexity
Class instantiation
Defining an object literal
Using the Function
type
Conclusion @TODO
We have seen only the design patterns that are commonly used in application development. There are lot of other design patterns available in software development.
Resources
- Understanding design patterns in TypeScript and Node.js: https://blog.logrocket.com/understanding-design-patterns-typescript-node-js/