This article introduces 5 TypeScript design patterns. There is a certain reference value, and friends in need can refer to it, and I hope it will be helpful to everyone.
Design patterns are templates that help developers solve problems. There are too many patterns covered in this book, and they often target different needs. However, they can be divided into three distinct groups:
- Structural patterns deal with relationships between different components (or classes) and form new structures, to provide new functionality. Examples of structural patterns are
Composite
,Adapter
, andDecorator
. - Behavioral patterns abstract the common behavior between components into an independent entity. Examples of behavioral patterns are commands, policies, and one of my personal favorites: the
Observer pattern
. - Creation mode focuses on the instantiation of classes, making it easier for us to create new entities. I’m talking about factory methods, singletons and abstract factories.
Singleton pattern
The singleton pattern is probably one of the most famous design patterns. It is a creational pattern because it ensures that no matter how many times we try to instantiate a class, we only have one instance available.
Singleton patterns for handling things like database connections, since we want to handle one at a time without having to reconnect on each user request.
class MyDBConn { protected static instance: MyDBConn | null = null private id: number = 0 constructor() { this.id = Math.random() } public getID():number { return this.id } public static getInstance():MyDBConn { if (!MyDBConn. instance) { MyDBConn. instance = new MyDBConn() } return MyDBConn.instance } } const cOnconnections = [ MyDBConn. getInstance(), MyDBConn. getInstance(), MyDBConn. getInstance(), MyDBConn. getInstance(), MyDBConn. getInstance() ] connections.forEach( c => { console. log(c. getID()) })
Now, although the class cannot be instantiated directly, using the getInstance
method, you can ensure that there will not be multiple instances. In the example above, you can see how a pseudo-class wrapping a database connection can benefit from this pattern.
This example shows that no matter how many times we call the getInstance
method, the connection is always the same.
The result of the above operation:
0.4047087250990713 0.4047087250990713 0.4047087250990713 0.4047087250990713 0.4047087250990713
Factory Mode
Factory Mode
is a creation mode, just like Singleton Mode
the same. However, this pattern doesn’t work directly on the object we care about, but only manages its creation.
Explain: Suppose we simulate moving vehicles by writing code. There are many types of vehicles, such as cars, bicycles and airplanes. The mobile code should be encapsulated in each vehicle
class. But the code that calls their move
methods can be generic.
The question here is how to handle object creation? There could be a single creator
class with 3 methods, or one method that takes parameters. In either case, extending that logic to support the creation of more vehices
requires growing the same class over time.
However, if you decide to use the factory method pattern, you can do the following:
Now, the code needed to create new objects is encapsulated into a new class, one for each vehicle type. This ensures that if vehicles need to be added in the future, only a new class needs to be added without modifying anything that already exists.
Let’s see how we can use TypeScript
to achieve this:
interface Vehicle {
move(): void
}
class Car implements Vehicle {
public move(): void {
console.log("Moving the car!")
}
}
class Bicycle implements Vehicle {
public move(): void {
console.log("Moving the bicycle!")
}
}
class Plane implements Vehicle {
public move(): void {
console.log("Flying the plane!")
}
}
// VehicleHandler is "abstract" because no one will instantiate it
// we're going to extend it and implement the abstract method
abstract class VehicleHandler {
// This is the method that the real handler needs to implement
public abstract createVehicle(): Vehicle
public moveVehicle(): void {
const myVehicle = this. createVehicle()
myVehicle. move()
}
}
class PlaneHandler extends VehicleHandler{
public createVehicle(): Vehicle {
return new Plane()
}
}
class CarHandler extends VehicleHandler{
public createVehicle(): Vehicle {
return new Car()
}
}
class BicycleHandler extends VehicleHandler{
public createVehicle(): Vehicle {
return new Bicycle()
}
}
/// User code...
const planes = new PlaneHandThe rator class is abstract
, which means it is not used, but is used to define a constructor that will keep a copy of the original object in a protected property. The overriding of the public interface is done inside the custom decorator.
SuperAnimal
and SwimmingAnimal
are the actual decorators, which are decorators that add extra behavior. The advantage of having this set up is that since all decorators also indirectly extend the Animal
class, if you want to mix the two behaviors together, you can Do the following:
const superSwimmingDog = new SwimmingAnimal(superDog) superSwimmingDog.move()
Composite (combination)
About the Composite mode, it is actually a combination mode, also known as a partial overall mode. This mode is in our It is also often used in life.
For example, if you have written a front-end page, you must have used tags such as
to define some formats, and then the formats are combined with each other and organized into corresponding ones in a recursive way Structure, this method is actually a combination, embedding part of the components into the whole.
The interesting thing about this pattern is that it is not a simple group of objects, it can contain entities or groups of entities, and each group can contain more groups at the same time, this is what we call a tree.
Look at an example:
interface IProduct { getName(): string getPrice(): number } class Product implements IProduct { private price: number private name: string constructor(name: string, price: number) { this.name = name this.price = price } public getPrice():number { return this.price } public getName(): string { return this.name } } class Box implements IProduct { private products: IProduct[] = [] constructor() { this. products = [] } public getName(): string { return "A box with " + this. products. length + " products" } add(p: IProduct): void { console.log("Adding a ", p.getName(), "to the box") this. products. push(p) } getPrice(): number { return this. products. reduce( (curr: number, b: IProduct) => (curr + b. getPrice()), 0) } } //Using the code... const box1 = new Box() box1. add(new Product("Bubble gum", 0.5)) box1.add(new Product("Samsung Note 20", 1005)) const box2 = new Box() box2.add( new Product("Samsung TV 20in", 300)) box2.add( new Product("Samsung TV 50in", 800)) box1. add(box2) console.log("Total price: ", box1.getPrice())
In the above example, we can put product
into Box
, you can also put Box
inside other Box
, which is a classic example of composition. Because what we want to achieve is to get the complete delivery price, so we need to add the price of each element (including the price of each small box
) in the big box
.
The result of the above operation:
Adding a Bubble gum to the box Adding a Samsung Note 20 to the box Adding a Samsung TV 20in to the box Adding a Samsung TV 50in to the box Adding a A box with 2 products to the box Total price: 2105.5
Therefore, consider using this pattern when dealing with multiple objects conforming to the same interface. By hiding the complexity within a single entity (the composition itself), you'll find it helps simplify how you interact with your group.
That's all for today's sharing, thank you for watching, see you next time.
Original address: https://blog.bitsrc.io/design-patterns-in-typescript-e9f84de40449
Author: Fernando Doglio
Translation address: https://segmentfault.com/a/1190000025184682
For more programming knowledge, please visit: Programming courses! !
The above are the details of the 5 design patterns of TypeScript that need to be understood. For more, please pay attention to other related articles on 1024programmer.com!