Génération de modèles de conception dans ES6 + sur l'exemple de Game of Thrones



Modèles de conception - façons de résoudre les problèmes les plus courants dans le développement de logiciels. Dans cet article, nous examinerons les modèles génératifs avec des références au Game of Thrones.

Lisez à propos des modèles structurels ici .

La génération de modèles est conçue pour fonctionner avec des mécanismes de construction d'objets afin de créer un objet de la manière la plus appropriée dans cette situation.

Les schémas génératifs les plus courants sont les suivants:

  • Usine (tissu)
  • Abstraction (Résumé)
  • Constructeur (constructeur)
  • Prototype (Prototype)
  • Singleton (Singleton)

Usine


Une fabrique est un modèle qui utilise les méthodes dites de fabrique pour créer des objets sans avoir besoin de déterminer la classe de l'objet créé. Qu'est-ce que ça veut dire?

Imaginez que nous voulons pouvoir créer des soldats. Ces soldats peuvent provenir soit de la maison Targaryen, soit de la maison Lannister.

Pour cela, nous avons besoin de:

  • Interface pour définir les soldats
  • Une classe pour chaque type de soldat pour responsabiliser chaque foyer
  • Classe pour demander la création d'un nouveau soldat, quelle que soit la maison à laquelle il appartient

class Soldier {
    constructor(name) {
        this.name = name
        this.attack = this.attack.bind(this)
    }

    attack() {}
}

class LannisterSoldier extends Soldier {
    attack() {
        return 'Lannister always pays his debts'
    }
}

class TargaryenSoldier extends Soldier {
    attack() {
        return 'Fire and blond'
    }
}

class Spawn {
    constructor(type, name) {
        if (type === 'lannister') return new LannisterSoldier(name)
        else return new TargaryenSoldier(name)
    }
}

(() => {
    const lannister = new Spawn('lannister', 'soldier1')
    const targaryen = new Spawn('targaryen', 'soldier2')
    console.log(lannister.attack())
    console.log(targaryen.attack())
})()

Quand est-il utilisé?


  • ,
  • ,


Avertissement: Un modèle abstrait basé sur des objets. Il est extrêmement difficile à utiliser en programmation fonctionnelle.

Ce modèle vous permet d'encapsuler un groupe d'usines individuelles qui effectuent des tâches similaires, sans avoir besoin de définir des classes spécifiques. En utilisation standard, le logiciel client crée une implémentation spécifique de la fabrique abstraite, puis utilise l'interface générale de la fabrique pour créer des objets spécifiques dans le cadre d'un système unique. Le client ne sait pas (ou n'a pas d'importance pour lui) quels objets spécifiques il reçoit de chaque usine interne, car une interface commune est utilisée pour cela.

Imaginez que nous voulons gérer chaque maison individuellement. Chaque maison devrait avoir la possibilité de déterminer l'héritier du trône et son bien-aimé.

Pour cela, nous avons besoin de:

  • ,

//  
class HeirToTheThrone {
    conctructor(name, isActualOnTheThrone) {
        this.name = name
        this.isActualOnTheThrone = isActualOnTheThrone
        this.getTheThrone = this.getTheThrone.bind(this)
    }
    getTheThrone() {}
}

class HeirToTheThroneLannister extends HeirToTheThrone {
    getTheThrone(){
        console.log('kill all')
    }
}

class HeirToTheThroneTargaryen extends HeirToTheThrone {
    getTheThrone() {
        console.log('burn all')
    }
}

//  
class Subject {
    constructor(name) {
        this.name = name
        this.speak = this.speak.bind(this)
    }
    speak() {}
}

class SubjectLannister extends Subject {
    speak(){
        console.log('i love Cersei')
    }
}

class SubjectTargaryen extends Subject {
    speak(){
        console.log('i love Daenerys')
    }
}

//  
class House {
    constructor() {
        this.getHeir = this.getHeir.bind(this)
        this.getSubject = this.getSubject.bind(this)
    }
    getHeir(){}
    getSubject(){}
}

class Lannister extends House {
    getHeir() {
        return new HeirToTheThroneLannister('Cersei', true)
    }
    getSubject(name) {
        return new SubjectLannister(name)
    }
}

class Targaryen extends House {
    getHeir() {
        return new HeirToTheThroneTargaryen('Daenerys', true)
    }
    getSubject(name) {
        return new SubjectTargaryen(name)
    }
}

(()=>{
    const lannister = new Lannister()
    const targaryen = new Targaryen()
    lannister.getHeir().getTheThrone()
    lannister.getSubject().speak()
    targaryen.getHeir().getTheThrone()
    targaryen.getSubject().speak()
})()

?


  • ,
  • ,


Le but du designer est de séparer un objet complexe de ses représentations. Avec la complexité de l'objet, ce modèle vous permet de séparer le processus de création d'un nouvel objet à travers un autre objet (constructeur).

Imaginez que nous voulons créer une flotte pour chaque maison. Chaque famille aura un certain nombre de navires et de guerriers. Pour faciliter la création, nous devrions pouvoir appeler la méthode makeNavy, qui créera automatiquement tout ce dont nous avons besoin via les paramètres.

Pour cela, nous avons besoin de:

  • Classe pour créer une flotte
  • Interface pour la définition du constructeur
  • La classe responsable de la création des objets de notre armée
  • Classes responsables de la création de soldats et de navires

class Lannister {
    constructor() {
        this.soldiers = []
        this.ships = []
        this.makeNavy = this.makeNavy.bind(this)
    }
    makeNavy(soldiers, ships) {
        const Build = new ConcreteBuilder()
        for (let i = 0; i < soldiers; i++) {
            this.soldiers.push(Build.createSoldier())
        }
        for (let i = 0; i < ships; i++) {
            this.ships.push(Build.createShip())
        }
    }
}

class Builder {
    createSoldier() {}
    createShip() {}
}

class ConcreteBuilder extends Builder {
    createSoldier() {
        const soldier = new Soldier()
        return soldier
    }
    createShip() {
        const ship = new Ship()
        return ship
    }
}

class Soldier {
    constructor() {
        console.log('soldier created')
    }
}

class Ship {
    constructor() {
        console.log('ship created')
    }
}

(() => {
    const lannister = new Lannister()
    lannister.makeNavy(100, 10)
})()

Quand est-il utilisé?


  • Lorsque le processus de création d'un objet est très complexe, il implique un grand nombre de paramètres obligatoires et facultatifs
  • Lorsqu'une augmentation du nombre de paramètres constructeurs entraîne une augmentation du nombre de constructeurs
  • Lorsque le client attend des vues différentes pour l'objet construit

Prototype


Le prototype vous permet de déterminer les types d'objets créés par héritage de prototype et de créer de nouveaux objets en utilisant le schéma d'un objet existant. Cela améliore les performances et minimise la perte de mémoire.

Imaginez que nous voulons créer une armée de marcheurs blancs. Nous n'avons pas d'exigences particulières pour cette armée. Nous voulons juste beaucoup d'entre eux, qu'ils aient les mêmes caractéristiques et qu'ils n'occupent pas beaucoup d'espace mémoire.

Pour cela, nous avons besoin de:

  • Classe de stockage d'informations sur les marcheurs blancs
  • Méthode de clonage d'une instance renvoyant la même méthode

class WhiteWalker {
    constructor(force, weight) {
        this.force = force
        this.weight = weight
    }

    clone() {
        console.log('cloned')
        return new WhiteWalker(this.name, this.weight)
    }
}

(()=>{
    const firstWalker = new WhiteWalker()
    const walkers = [firstWalker]
    for(let i=0;i<100;i++){
        walkers.push(firstWalker.clone())
    }
})()

Avantages et inconvénients du prototype


Avantages:

  • Aide à réduire les coûts, le temps et la productivité en éliminant la nécessité d'utiliser un nouvel opérateur pour créer de nouveaux objets
  • Réduit la complexité de l'initialisation d'un objet: chaque classe utilise sa propre méthode de clonage
  • Il n'est pas nécessaire de classer et de créer de nombreuses sous-classes pour initialiser des objets comme lors de l'utilisation d'un modèle abstrait
  • Augmente la flexibilité du système en créant de nouveaux objets en modifiant certaines propriétés de l'objet copié

Moins:

  • Le clonage d'objets complexes avec des références circulaires est une tâche non triviale

Singleton


Singleton vous permet de vous assurer que l'objet créé est la seule instance d'une classe particulière. Un tel objet est généralement utilisé pour contrôler plusieurs opérations dans le système.

Imaginez que nous voulons être sûrs d'avoir une seule mère dragon.

Pour cela, nous avons besoin de:

  • Cours pour stocker des informations sur la mère des dragons
  • Instance enregistrée de Dragon Mother

let INSTANCE = null

class MotherOfDragons {
    constructor(name, dragons) {
        if(!!INSTANCE) return INSTANCE
        this.dragons = dragons
        this.name = name
        this.getMyDragons = this.getMyDragons.bind(this)
        INSTANCE = this
    }

    getMyDragons(){
        console.log(`I'm ${this.name} and my dragons are`, this.dragons)
    }
}

(()=>{
    const dragonMother = new MotherOfDragons('Daenerys Targaryen', [
        'Drogon',
        'Rhaegal',
        'Viserion'
    ])
    const dragonMother2 = new MotherOfDragons('Cercei Targaryen', [
        'Tirion',
        'Jennifer',
        'Goblin'
    ])
    dragonMother.getMyDragons()
    dragonMother2.getMyDragons()
    console.log(dragonMother instanceof MotherOfDragons)
    console.log(dragonMother2 instanceof MotherOfDragons)
    console.log(dragonMother2 === dragonMother)
})()

Quand est-il utilisé?


  • Lorsque les ressources utilisées pour créer l'objet sont limitées (par exemple, les objets de connexion à la base de données)
  • Il est recommandé de créer une autorisation avec un singleton pour améliorer les performances.
  • Lorsque vous devez créer une classe pour configurer l'application
  • Lorsque vous devez créer une classe pour l'allocation des ressources

Code Github .

Remarque trans: voici une superbe vidéo sur les modèles de conception.

Merci de votre attention.

All Articles