Les modèles de conception sont des solutions aux problèmes récurrents dans le développement d’applications logicielles.

Comme nous le savons tous, il existe trois types de modèles de conception. Vous êtes:

  • Créatif
  • Structurellement
  • comportement

Mais attendez. Qu’est-ce que ça veut dire?

Modèle de création traite de la façon dont nous créons des objets dans un style orienté objet. Il applique des modèles comme nous instancions une classe.

Motif structurel traite de la façon dont nos classes et objets sont assemblés pour former une structure plus grande dans notre application.

Modèles de comportement se préoccupe de la façon dont les objets peuvent interagir efficacement sans être étroitement liés.

Un organigramme montrant les trois types de modèles de conception parmi lesquels nous pouvons choisir.

Ce didacticiel vous guidera à travers certains des modèles de conception les plus courants que vous pouvez utiliser dans votre application Node.js. Nous utiliserons Typescript pour faciliter la mise en œuvre.

Singleton

Le modèle singleton implique qu’il ne devrait y avoir qu’une seule instance d’une classe. Pour les laïcs, il ne devrait y avoir qu’un seul président par pays. En suivant ce modèle, vous pouvez éviter d’avoir plusieurs instances d’une classe donnée.

Un bon exemple du modèle singleton est la connexion à la base de données dans notre application. S’il existe plusieurs instances d’une base de données dans notre application, une application devient instable. Ainsi, le modèle singleton fournit une solution à ce problème en conservant une seule instance dans l’application.

Les deux caractéristiques du modèle de conception singleton.

Voyons comment l’exemple ci-dessus est implémenté dans Node.js à l’aide de Typescript:

import {MongoClient,Db} from 'mongodb'
class DBInstance {

    private static instance: Db

    private 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
    }
}

export default DBInstance

Ici nous avons une classe DBInstanceavec une instance d’attribut. dans le DBInstanceNous avons la méthode statique getInstance où réside notre principale logique.

Il est vérifié si une instance de base de données existe déjà. Si tel est le cas, il le renverra. Sinon, une instance de base de données sera créée et renvoyée pour nous.

Voici un exemple de la façon dont nous pouvons utiliser cela singleton Modèles dans nos routes API:

import express,{  Application, Request, Response } from 'express'
import DBInstance from './helper/DB'
import bodyParser from 'body-parser'
const app = express()

async function start(){

    try{

        app.use(bodyParser.json())
        app.use(bodyParser.urlencoded({extended : true}))
        const db = await DBInstance.getInstance()

        app.get('/todos',async (req : Request,res : Response) => {
            try {
                const db = await DBInstance.getInstance()

                const todos = await db.collection('todo').find({}).toArray()
                res.status(200).json({success : true,data : todos})
            }
            catch(e){
                console.log("Error on fetching",e)
                res.status(500).json({ success : false,data : null })
            }
            
        })

        app.post('/todo',async (req : Request,res : Response) => {
            try {
                const db = await DBInstance.getInstance()

                const todo = req.body.todos
               const todoCollection =  await db.collection('todo').insertOne({ name : todo })

                res.status(200).json({ success : true,data : todoCollection })
            }
            catch(e){
                console.log("Error on Inserting",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()

Usine abstraite

Avant d’entrer dans l’explication de l’usine abstraite, je veux que vous sachiez ce que cela signifie factory pattern.

Usine simple

Permettez-moi de faire une analogie par souci de simplicité. Disons que vous avez faim et que vous voulez manger quelque chose. Vous pouvez cuisiner pour vous-même ou commander dans un restaurant. De cette façon, vous n’avez pas besoin d’apprendre ou de savoir cuisiner pour manger quelque chose.

De même, le modèle de fabrique génère simplement une instance d’objet pour un utilisateur sans fournir de logique d’instanciation au client.

Maintenant que nous connaissons le modèle d’usine simple, revenons au modèle d’usine abstrait. Disons que vous avez faim et que vous avez décidé de commander de la nourriture dans un restaurant. Si vous le souhaitez, vous pouvez également commander une autre cuisine. Ensuite, vous devrez peut-être choisir le meilleur restaurant en fonction de la cuisine.

Comme vous pouvez le voir, il y a une dépendance entre votre nourriture et le restaurant. Différents restaurants conviennent mieux à différentes cuisines.

Mettons en œuvre un abstract factory Pattern dans notre application Node.js. Nous allons maintenant construire un magasin d’ordinateurs portables avec différents types d’ordinateurs. Certains des principaux composants sont Storage et Processor.

Créons une interface pour cela:

export default interface IStorage {
     getStorageType(): string
}
import IStorage from './IStorage'
export default interface IProcessor {
    attachStorage(storage : IStorage) : string

    showSpecs() : string
}

Ensuite, nous mettrons en œuvre cela storage et processor Interfaces dans les classes:

import IProcessor from '../../Interface/IProcessor'
import IStorage from '../../Interface/IStorage'

export default class MacbookProcessor implements IProcessor {

    storage: string | undefined

    MacbookProcessor() {
        console.log("Macbook is built using apple silicon chips")    
    }

    attachStorage(storageAttached: IStorage) {
        this.storage = storageAttached.getStorageType()
        console.log("storageAttached",storageAttached.getStorageType())
        return this.storage+" Attached to Macbook"
    }
    showSpecs(): string {
        return this.toString()
    }

    toString() : string {
        return "AppleProcessor is created using Apple Silicon and "+this.storage;
    }

}
import IProcessor from '../../Interface/IProcessor'
import IStorage from '../../Interface/IStorage'

export default class MacbookStorage implements IStorage {

    storageSize: number

    constructor(storageSize : number) {
        this.storageSize = storageSize
        console.log(this.storageSize+" GB SSD is used")
    }

    getStorageType() {
        return  this.storageSize+"GB SSD"
    }

}

Créons maintenant une interface d’usine avec des méthodes comme createProcessor et createStorage.

import IStorage from '../Interface/IStorage'
import IProcessor from '../Interface/IProcessor'

export default interface LaptopFactory {
    createProcessor() : IProcessor

    createStorage() : IStorage
}

Une fois l’interface d’usine créée, implémentez-la dans la classe laptop. Ici ce sera:

import LaptopFactory from '../../factory/LaptopFactory'
import MacbookProcessor from './MacbookProcessor'
import MacbookStorage from './MacbookStorage'

export class Macbook implements LaptopFactory {
    storageSize: number;

    constructor(storage : number) {
        this.storageSize = storage
    }

    createProcessor() : any{
        return new MacbookProcessor()
    }

    createStorage(): any {
        return new MacbookStorage(this.storageSize)
    }
}

Enfin, créez une fonction qui appelle les méthodes de fabrique:

import LaptopFactory from '../factory/LaptopFactory'
import IProcessor from '../Interface/IProcessor'

export const buildLaptop =  (laptopFactory : LaptopFactory) : IProcessor => {
    const processor = laptopFactory.createProcessor()

    const storage = laptopFactory.createStorage()

    processor.attachStorage(storage)

    return processor
}

Modèle de constructeur

Le modèle de générateur vous permet de créer différentes variantes d’un objet sans utiliser de constructeur dans une classe.

Mais pourquoi ne pouvons-nous pas simplement utiliser un constructeur?

Eh bien, il y a un problème avec ça constructor dans certains scénarios. Disons que vous en avez un User Modèle et il a des attributs comme:

export default class User {

    firstName: string
    lastName : string
    gender: string
    age: number
    address: string
    country: string
    isAdmin: boolean

    constructor(firstName,lastName,address,gender,age,country,isAdmin) {
        this.firstName = builder.firstName
        this.lastName = builder.lastName
        this.address = builder.address
        this.gender = builder.gender
        this.age = builder.age
        this.country = builder.country
        this.isAdmin = builder.isAdmin
    }

}

Pour l’utiliser, vous devrez peut-être l’instancier comme ceci:

const user = new User("","","","",22,"",false)

Ici, nous avons un argument limité. Cependant, ceux-ci seront difficiles à maintenir une fois que les attributs augmenteront. Pour résoudre ce problème, nous avons besoin du modèle de générateur.

Créez une classe de générateur comme suit:

import User from './User'

export default class UserBuilder {

    firstName = ""
    lastName = ""
    gender = ""
    age = 0
    address = ""
    country = ""
    isAdmin = false

    constructor(){
        
    }

    setFirstName(firstName: string){
        this.firstName = firstName
    }

    setLastName(lastName : string){
        this.lastName = lastName
    }

    setGender(gender : string){
        this.gender = gender
    }

    setAge(age : number){
        this.age = age
    }

    setAddress(address : string){
        this.address = address
    }

    setCountry(country : string){
        this.country = country
    }

    setAdmin(isAdmin: boolean){
        this.isAdmin = isAdmin
    }

    build() : User {
        return new User(this)
    }

    getAllValues(){
        return this
    }
}

Ici nous utilisons getter et setter pour gérer les attributs de notre classe de générateur. Après cela, utilisez la classe Builder dans notre modèle:

import UserBuilder from './UserBuilder'

export default class User {

    firstName: string
    lastName : string
    gender: string
    age: number
    address: string
    country: string
    isAdmin: boolean

    constructor(builder : UserBuilder) {
        this.firstName = builder.firstName
        this.lastName = builder.lastName
        this.address = builder.address
        this.gender = builder.gender
        this.age = builder.age
        this.country = builder.country
        this.isAdmin = builder.isAdmin
    }

}

adaptateur

Un exemple classique de modèle d’adaptateur est une prise de forme différente. Parfois, la prise et la fiche de l’appareil ne correspondent pas. Pour nous assurer que cela fonctionne, nous utiliserons un adaptateur. C’est exactement ce que nous allons faire dans le modèle d’adaptateur.

C’est un processus d’encapsulation de l’objet incompatible dans un adaptateur pour le rendre compatible avec une autre classe.

Jusqu’à présent, nous avons vu une analogie pour aider à comprendre le modèle d’adaptateur. Permettez-moi de vous donner un cas d’utilisation dans le monde réel où un modèle d’adaptateur peut sauver des vies.

Rappelez-vous que nous en avons un CustomerError Classer:

import IError from '../interface/IError'
export default class CustomError implements IError{

    message : string

    constructor(message : string){
        this.message = message
    }

    serialize() {
        return this.message
    }
}

Maintenant, utilisons ceci CustomError Super pour notre application. Après un certain temps, nous devons changer la méthode dans la classe pour une raison quelconque.

New Custom Error La classe sera quelque chose comme ceci:

export default class NewCustomError{

    message : string
    
    constructor(message : string){
        this.message = message    
    }

    withInfo() {
        return { message : this.message } 
    }
}

Notre nouvelle modification bloque l’ensemble de l’application car elle modifie la méthode. Pour résoudre ce problème, le modèle d’adaptateur entre en jeu.

Créons une classe d’adaptateur et résolvons ce problème:

import NewCustomError from './NewCustomError'
// import CustomError from './CustomError'
export default class ErrorAdapter {
    message : string;
    constructor(message : string) {
        this.message = message
    }

    serialize() {
              // In future replace this function
        const e = new NewCustomError(this.message).withInfo()
        return e
    }

}

le serialize Nous utilisons la méthode tout au long de notre application. Notre application n’a pas besoin de savoir quelle classe nous utilisons. le Adapter La classe prend soin de nous.

observateur

Un modèle d’observateur est un moyen de mettre à jour les éléments dépendants lorsqu’un statut change dans un autre objet. il contient généralement Observer et Observable. Observer abonné Observable et en cas de changement, Observable en informe les observateurs.

Observable.

Pour comprendre ce concept, prenons un cas d’utilisation du monde réel pour le modèle d’observateur:

Auteur.

Ici nous avons Author, Tweet, et follower Entités. Followers peut souscrire Author . Chaque fois qu’il y en a un nouveau Tweet, les follower Est mis à jour.

Implémentons-le dans notre application Node.js:

import Tweet from "../module/Tweet";

export default interface IObserver {
    onTweet(tweet : Tweet): string
}
import Tweet from "../module/Tweet";

export default interface IObservable{

    sendTweet(tweet : Tweet): any
}

Ici nous avons l’interface IObservable et IObserver, elle a onTweet et sendTweet Méthodes dedans.

import IObservable from "../interface/IObservable";
import Tweet from "./Tweet";
import Follower from './Follower'
export default class Author implements IObservable {

    protected observers : Follower[] = []

    notify(tweet : Tweet){
        this.observers.forEach(observer => {
            observer.onTweet(tweet)
        })
    }

    subscribe(observer : Follower){
        this.observers.push(observer)
    }

    sendTweet(tweet : Tweet) {
        this.notify(tweet)
    }
}

Follower.ts

import IObserver from '../interface/IObserver'
import Author from './Author'
import Tweet from './Tweet'

export default class Follower implements IObserver {

    name : string

    constructor(name: string){
        this.name = name
    }

    onTweet(tweet: Tweet) {
        console.log( this.name+" you got tweet =>"+tweet.getMessage())
        return this.name+" you got tweet =>"+tweet.getMessage()
    }

}

Et Tweet.ts::

export default class Tweet {

    message : string
    author: string

    constructor(message : string,author: string) {
        this.message = message
        this.author= author
    }

    getMessage() : string {
        return this.message+" Tweet from Author: "+this.author
    }
}

index.ts

import express,{  Application, Request, Response } from 'express'
// import DBInstance from './helper/DB'
import bodyParser from 'body-parser'
import Follower from './module/Follower'
import Author from './module/Author'
import Tweet from './module/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()

Modèle de stratégie

Le modèle de stratégie vous permet de choisir un algorithme ou une stratégie au moment de l’exécution. Le cas d’utilisation réel de ce scénario serait de changer la stratégie de stockage de fichiers en fonction de la taille du fichier.

Notez que vous souhaitez gérer le stockage des fichiers en fonction de la taille des fichiers dans votre application:

Téléchargez la taille du fichier.

Ici, nous voulons télécharger le fichier et définir la stratégie en fonction de la taille du fichier, qui est une condition d’exécution. Implémentons ce concept avec un strategy Modèle.

Créer une interface à implémenter Writer Classer:

export default interface IFileWriter {
    write(filepath: string | undefined) : boolean
}

Puis créez-en un class pour traiter les fichiers lorsqu’ils sont plus volumineux:

import IFileWriter from '../interface/IFileWriter'

export default class AWSWriterWrapper implements IFileWriter {

    write() {
        console.log("Writing File to AWS S3")
        return true
    }
}

Puis créez-en un class pour traiter des fichiers plus petits:

import IFileWriter from '../interface/IFileWriter'

export default class DiskWriter implements IFileWriter {

    write(filepath : string) {
        console.log("Writing File to Disk",filepath)
        return true
    }
}

Une fois que nous avons les deux, nous devons créer le client que tout le monde peut utiliser strategy à l’intérieur:

import IFileWriter from '../interface/IFileWriter'

export default class Writer {

    protected writer
    constructor(writer: IFileWriter) {
        this.writer = writer
    }

    write(filepath : string) : boolean {
        return this.writer.write(filepath)
    }
}

Enfin, nous pouvons utiliser la stratégie basée sur la condition que nous avons:

let size = 1000

                if(size < 1000){
                    const writer = new Writer(new DiskFileWriter())
                    writer.write("file path comes here")
                }
                else{
                    const writer = new Writer(new AWSFileWriter())
                    writer.write("writing the file to the cloud")
                }

Chaîne de responsabilité

La chaîne de responsabilité permet à un objet de passer par une chaîne de conditions ou de fonctions. Au lieu de gérer toutes les fonctions et conditions en un seul endroit, il est décomposé en chaînes de conditions qu’un objet doit traverser.

L’un des meilleurs exemples de ce modèle est celui-ci express middleware::

Intergiciel express.

Nous construisons des fonctions en tant que middleware qui est lié à chaque requête dans l’Express. Notre demande doit remplir la condition dans le middleware. C’est le meilleur exemple d’un chain of responsibility::

façade

Le modèle de façade nous permet de combiner des fonctions ou des modules similaires dans une seule interface. De cette façon, le client n’a pas besoin de savoir quoi que ce soit sur son fonctionnement en interne.

Un bon exemple de ceci serait lorsque votre ordinateur démarre. Vous n’avez pas besoin de savoir ce qui se passe à l’intérieur de l’ordinateur lorsque vous l’allumez. Tout ce que vous avez à faire est d’appuyer sur un bouton. De cette manière, le modèle de façade nous aide à créer une logique de haut niveau sans que le client n’ait à tout mettre en œuvre.

À titre d’exemple, prenons un profil utilisateur dans lequel nous avons les fonctions suivantes:

  • Chaque fois qu’un compte utilisateur est désactivé, nous devons mettre à jour le statut et mettre à jour les coordonnées bancaires.

Utilisons ça facade Exemple d’implémentation de cette logique dans notre application:

import IUser from '../Interfaces/IUser'

export default class User {
    private firstName: string
    private lastName: string
    private bankDetails: string | null
    private age: number
    private role: string
    private isActive: boolean

    constructor({firstName,lastName,bankDetails,age,role,isActive} : IUser){
        this.firstName = firstName
        this.lastName = lastName
        this.bankDetails = bankDetails
        this.age = age
        this.role = role
        this.isActive = isActive
    }

    getBasicInfo() {
        return {
            firstName: this.firstName,
            lastName: this.lastName,
            age : this.age,
            role: this.role
        }
    }

    activateUser() {
        this.isActive = true
    }

    updateBankDetails(bankInfo: string | null) {
        this.bankDetails= bankInfo
    }

    getBankDetails(){
        return this.bankDetails
    }

    deactivateUser() {
        this.isActive = false
    }

    getAllDetails() {
        return this
    }
}
export default interface IUser {
    firstName: string
    lastName: string
    bankDetails: string
    age: number
    role: string
    isActive: boolean
}

Enfin le nôtre facade La classe sera:

import User from '../module/User'

export default class UserFacade{

    protected user: User
    constructor(user : User){
        this.user = user
    }

    activateUserAccount(bankInfo : string){
        this.user.activateUser()
        this.user.updateBankDetails(bankInfo)

        return this.user.getAllDetails()
    }

    deactivateUserAccount(){
        this.user.deactivateUser()
        this.user.updateBankDetails(null)
    }

}

Ici, nous combinons les méthodes que nous devons appeler lorsqu’un compte utilisateur est désactivé.

Vous pouvez trouver le code source complet Ici.

Conclusion

Nous n’avons vu que les modèles de conception couramment utilisés dans le développement d’applications. De nombreux autres modèles de conception sont disponibles dans le développement de logiciels. N’hésitez pas à commenter votre modèle de conception et votre cas d’utilisation préférés.

Seulement 200s Surveiller les demandes réseau échouées et lentes en production

Le déploiement d’une application Web ou d’un site Web basé sur des nœuds est la partie la plus simple. Si vous vous assurez que votre instance Node continue de fournir des ressources pour votre application, les choses se compliquent. Si vous souhaitez vous assurer que les requêtes adressées au backend ou à des services tiers aboutissent, Essayez LogRocket. https://logrocket.com/signup/

LogRocket est comme une application Web DVR qui enregistre littéralement tout ce qui se passe sur votre site Web. Au lieu de deviner pourquoi les problèmes se produisent, vous pouvez regrouper et signaler les demandes réseau problématiques pour comprendre rapidement la cause première.

LogRocket instruments votre application pour enregistrer les temps de performance de base tels que le temps de chargement de la page, le temps jusqu’au premier octet, les requêtes réseau lentes et les journaux / états de Redux, NgRx et Vuex. .



Source link

Recent Posts