Lorsqu'il s'agit d'écrire du code propre, maintenable et efficace, les modèles de conception jouent un rôle crucial dans le monde du développement logiciel. Les modèles de conception sont des solutions réutilisables aux problèmes courants auxquels les développeurs sont confrontés lors de la conception et de la création de systèmes logiciels. Ils offrent une approche structurée pour résoudre des défis spécifiques, facilitant ainsi la création d'un code non seulement robuste, mais également plus facile à comprendre et à maintenir.
Dans la programmation orientée objet (POO ), les modèles de conception servent de lignes directrices pour structurer votre code de manière à favoriser la flexibilité, la réutilisabilité et l'évolutivité. Ils résument les meilleures pratiques et les principes de conception qui ont évolué et se sont transformés en solutions éprouvées.
Les modèles de conception peuvent être classés en trois groupes principaux :
Modèles de création : ces modèles se concentrent sur les mécanismes de création d'objets, essayant de créer des objets d'une manière adaptée à la situation. Ils font abstraction du processus d'instanciation, le rendant plus flexible et indépendant du système.
Modèles structurels : les modèles structurels traitent de la composition des objets, formant des relations entre les objets pour créer des structures plus grandes et plus complexes. Ils aident à définir comment les objets et les classes peuvent être combinés pour former de nouvelles structures et fournir de nouvelles fonctionnalités.
Modèles comportementaux : les modèles comportementaux concernent la communication entre les objets, définissant la manière dont ils interagissent et répartissent les responsabilités. Ces modèles vous aident à concevoir des systèmes dans lesquels les objets collaborent de manière plus flexible et plus efficace.
Voici une liste de quelques modèles de conception courants dans chaque catégorie :
Modèle d'observateur : définit une dépendance un-à-plusieurs entre les objets. Ainsi, lorsqu'un objet change d'état, toutes ses dépendances sont notifiées et mises à jour automatiquement.
Modèle de stratégie : définit une famille d'algorithmes, encapsule chacun d'entre eux et les rend interchangeables.
Modèle de commande : encapsule une requête en tant qu’objet, permettant ainsi le paramétrage des clients avec des files d’attente, des requêtes et des opérations.
Modèle d'état : permet à un objet de modifier son comportement lorsque son état interne change, en encapsulant le comportement dans des classes distinctes.
Modèle de chaîne de responsabilité : transmet la demande le long d'une chaîne de gestionnaires, permettant à chaque gestionnaire de décider soit de traiter la demande, soit de la transmettre au gestionnaire suivant de la chaîne.
Modèle de visiteur : Il représente une opération à effectuer sur les éléments d'une structure d'objet, permettant de définir de nouvelles opérations sans changer les classes des éléments.
Dans ce blog, nous examinerons chacun de ces modèles de conception, en fournissant des explications, des cas d'utilisation réels et des exemples de code JavaScript pour vous aider à les comprendre et à les mettre en œuvre efficacement dans vos projets.
Le modèle Singleton est un modèle de conception créationnel qui garantit qu'une classe n'a qu'une seule instance et fournit un point d'accès global à cette instance. Ce modèle est particulièrement utile lorsque vous souhaitez limiter le nombre d'instances d'une classe dans votre application et contrôler l'accès à une seule instance partagée.
En JavaScript, l'implémentation du modèle Singleton est relativement simple, grâce à la flexibilité du langage. Examinons un exemple simple de création d'un Singleton en JavaScript.
// Singleton instance let instance = null;
class Singleton { constructor() { if (!instance) { instance = this; // Your initialization code here } else { return instance; } } // Your methods and properties here }// Usage const singletonA = new Singleton(); const singletonB = new Singleton(); console.log(singletonA === singletonB); // Output: true (both variables reference the same instance)
Dans cet exemple, nous créons une classe Singleton avec un constructeur qui vérifie si une instance existe déjà. Si une instance n'existe pas, il en crée une et l'attribue à la variable d'instance. Les appels suivants au constructeur renvoient l'instance existante, garantissant qu'il n'existe qu'une seule instance de la classe Singleton.
Le modèle Singleton est utile dans divers scénarios, notamment :
Bien que le modèle Singleton puisse être bénéfique, il est essentiel de l’utiliser judicieusement. Une utilisation excessive du modèle Singleton peut conduire à un code et un état global étroitement couplés, ce qui peut rendre votre application plus difficile à maintenir et à tester. Par conséquent, il est crucial de peser le pour et le contre et d’appliquer le modèle là où il ajoute véritablement de la valeur à votre base de code.
Le modèle d'usine et le modèle d'usine abstrait sont des modèles de conception créationnels qui traitent de la création d'objets, mais ils le font de différentes manières et servent des objectifs distincts. Explorons chacun de ces modèles et voyons comment ils peuvent être implémentés en JavaScript.
Le Factory Pattern est un modèle de création qui fournit une interface pour créer des objets mais permet aux sous-classes de modifier le type d'objets qui seront créés. Il encapsule le processus de création d'objet, le rendant plus flexible et découplé du code client.
// Product class class Product { constructor(name) { this.name = name; } }
// Factory for creating products class ProductFactory { createProduct(name) { return new Product(name); } }// Usage const factory = new ProductFactory(); const productA = factory.createProduct('Product A'); const productB = factory.createProduct('Product B');console.log(productA.name); // Output: 'Product A' console.log(productB.name); // Output: 'Product B'
Dans cet exemple, ProductFactory est responsable de la création d’instances de la classe Product. Il résume le processus de création, vous permettant de créer différents types de produits en agrandissant l'usine.
Le modèle Abstract Factory est un autre modèle de création qui fournit une interface permettant de créer des familles d'objets liés ou dépendants sans spécifier leurs classes concrètes. Il permet de créer des ensembles d'objets qui fonctionnent ensemble harmonieusement.
// Abstract Product classes class Button { render() {} }
class Checkbox { render() {} }// Concrete Product classes class MacButton extends Button { render() { return 'Render Mac button'; } }class MacCheckbox extends Checkbox { render() { return 'Render Mac checkbox'; } }class WindowsButton extends Button { render() { return 'Render Windows button'; } }class WindowsCheckbox extends Checkbox { render() { return 'Render Windows checkbox'; } }// Abstract Factory interface class GUIFactory { createButton() {} createCheckbox() {} }// Concrete Factories class MacFactory extends GUIFactory { createButton() { return new MacButton(); } createCheckbox() { return new MacCheckbox(); } }class WindowsFactory extends GUIFactory { createButton() { return new WindowsButton(); } createCheckbox() { return new WindowsCheckbox(); } }// Usage function createUI(factory) { const button = factory.createButton(); const checkbox = factory.createCheckbox(); return { button, checkbox }; }const macUI = createUI(new MacFactory()); console.log(macUI.button.render()); // Output: 'Render Mac button' console.log(macUI.checkbox.render()); // Output: 'Render Mac checkbox'const windowsUI = createUI(new WindowsFactory()); console.log(windowsUI.button.render()); // Output: 'Render Windows button' console.log(windowsUI.checkbox.render()); // Output: 'Render Windows checkbox'
Dans cet exemple, nous avons deux usines concrètes, MacFactory et WindowsFactory, chacune capable de créer un ensemble de composants d'interface utilisateur associés (boutons et cases à cocher) pour leurs plates-formes respectives. La fonction createUI vous permet de créer une interface utilisateur cohérente pour une plate-forme spécifique à l'aide de la fabrique appropriée.
Quand utiliser quel modèle :
Le Builder Pattern est un modèle de conception créationnel qui sépare la construction d'un objet complexe de sa représentation, permettant au même processus de construction de créer différentes représentations. Ce modèle est particulièrement utile lorsque vous disposez d'un objet avec un grand nombre de propriétés et que vous souhaitez simplifier la création d'instances tout en conservant de la flexibilité.
En JavaScript, le modèle Builder est souvent implémenté à l'aide d'une classe ou d'un objet générateur qui guide la construction étape par étape de l'objet complexe. Prenons un exemple pour comprendre comment cela fonctionne.
// Product class with multiple properties class Product { constructor() { this.name = ''; this.price = 0; this.color = 'white'; // ... other properties }
// Additional methods can be defined here }// Builder for creating Product instances class ProductBuilder { constructor() { this.product = new Product(); } setName(name) { this.product.name = name; return this; // Return the builder for method chaining } setPrice(price) { this.product.price = price; return this; } setColor(color) { this.product.color = color; return this; } // Other methods to set additional properties build() { return this.product; // Return the fully constructed product } }// Usage const builder = new ProductBuilder();const productA = builder .setName('Product A') .setPrice(99.99) .setColor('blue') .build();const productB = builder .setName('Product B') .setPrice(49.99) .build();console.log(productA); console.log(productB);
Dans cet exemple, nous avons une classe Product avec plusieurs propriétés. La classe ProductBuilder permet de créer des instances de Product en fournissant des méthodes pour définir chaque propriété étape par étape. Le chaînage de méthodes vous permet de définir plusieurs propriétés de manière fluide et lisible. Enfin, la méthode build renvoie l’instance Product entièrement construite.
Le modèle Builder est bénéfique dans divers scénarios, notamment :
Bien que le modèle Builder offre de nombreux avantages, il est important de noter qu'il ajoute de la complexité à votre base de code, surtout si les objets en cours de construction sont relativement simples. Par conséquent, il est essentiel d'évaluer si la complexité introduite par le Builder est justifiée pour votre cas d'utilisation spécifique.
Le modèle de prototype est un modèle de conception créationnel qui vous permet de créer de nouveaux objets en copiant un objet existant, appelé prototype. Il favorise la création d'objets sans préciser la classe exacte d'objet à créer. Ce modèle est particulièrement utile lorsque vous souhaitez créer efficacement des instances d'objets complexes.
En JavaScript, le modèle de prototype est étroitement lié à la propriété prototype
intégrée et à la méthode Object.create()
. Explorons comment implémenter et utiliser le modèle de prototype en JavaScript.
// Prototype object const vehiclePrototype = { init(make, model) { this.make = make; this.model = model; }, getDetails() { return `${this.make} ${this.model}`; }, };
// Create new instances using the prototype const car1 = Object.create(vehiclePrototype); car1.init('Toyota', 'Camry');const car2 = Object.create(vehiclePrototype); car2.init('Honda', 'Civic');console.log(car1.getDetails()); // Output: 'Toyota Camry' console.log(car2.getDetails()); // Output: 'Honda Civic'
Dans cet exemple, nous définissons un objet vehiclePrototype
avec des méthodes et propriétés communes à tous les véhicules. Nous utilisons Object.create()
pour créer de nouvelles instances (car1 et car2) basées sur ce prototype. Ces instances héritent des propriétés et des méthodes du prototype, ce qui vous permet de créer efficacement de nouveaux objets avec un comportement partagé.
Le modèle prototype est utile dans divers scénarios, notamment :
Bien que le modèle de prototype soit utile, il comporte quelques considérations :
Le modèle de pool d'objets est un modèle de conception créationnel qui gère un pool d'objets réutilisables afin de minimiser les frais de création et de destruction d'objets. C'est particulièrement utile lorsque la création et la destruction d'objets sont coûteuses ou gourmandes en ressources. Le modèle de pool d'objets permet d'améliorer les performances et l'utilisation des ressources en recyclant et en réutilisant les objets au lieu d'en créer de nouveaux à partir de zéro.
En JavaScript, vous pouvez implémenter le modèle de pool d'objets à l'aide de tableaux ou de classes de gestion de pool personnalisées. Explorons comment ce modèle fonctionne avec un exemple simple.
class ObjectPool { constructor(maxSize) { this.maxSize = maxSize; this.pool = []; }
create() { if (this.pool.length < this.maxSize) { // Create a new object and add it to the pool const obj = { /* Your object initialization code here */ }; this.pool.push(obj); return obj; } else { // Pool is full, cannot create more objects console.log('Pool is full. Cannot create more objects.'); return null; } } reuse() { if (this.pool.length > 0) { // Reuse an object from the pool return this.pool.pop(); } else { // Pool is empty, no objects available for reuse console.log('Pool is empty. No objects available for reuse.'); return null; } } release(obj) { // Release an object back to the pool for reuse this.pool.push(obj); } }// Usage const pool = new ObjectPool(5); // Create a pool with a maximum size of 5 objectsconst obj1 = pool.create(); const obj2 = pool.create(); const obj3 = pool.create();pool.release(obj2); // Release obj2 back to the pool for reuseconst obj4 = pool.reuse(); // Reuse an object from the pool (obj2)
Dans cet exemple, nous créons une classe ObjectPool qui gère un pool d'objets. La méthode create crée de nouveaux objets lorsque le pool n'est pas plein, la méthode reuse récupère un objet du pool pour le réutiliser et la méthode release renvoie un objet au pool pour une utilisation ultérieure.
Le modèle de pool d'objets est utile dans divers scénarios, notamment :
Bien que le modèle de pool d'objets offre des avantages en termes de performances, il est important de prendre en compte les éléments suivants :
Le modèle d'adaptateur est un modèle de conception structurelle qui permet aux objets dotés d'interfaces incompatibles de fonctionner ensemble. Il agit comme un pont entre deux interfaces incompatibles, les rendant compatibles sans changer leur code source. Ce modèle est particulièrement utile lorsque vous devez intégrer ou utiliser du code existant qui ne correspond pas tout à fait aux exigences de votre application.
En JavaScript, le modèle d'adaptateur peut être implémenté à l'aide de classes ou de fonctions qui enveloppent ou adaptent l'interface incompatible. Explorons comment implémenter et utiliser le modèle d'adaptateur en JavaScript avec un exemple pratique.
Supposons que vous ayez une classe existante appelée OldSystem
avec une méthode appelée legacyRequest
:
class OldSystem { legacyRequest() { return 'Data from the legacy system'; } }
Vous souhaitez désormais utiliser ce système existant dans votre application moderne qui attend une interface différente. Vous pouvez créer une classe ou une fonction d'adaptateur comme ceci :
class Adapter { constructor(oldSystem) { this.oldSystem = oldSystem; }
newRequest() { const legacyData = this.oldSystem.legacyRequest(); // Adapt the data or perform any necessary transformations return `Adapted: ${legacyData}`; } }
Désormais, vous pouvez utiliser la classe Adapter pour rendre le système existant compatible avec votre application moderne :
const oldSystem = new OldSystem(); const adapter = new Adapter(oldSystem);
const result = adapter.newRequest(); console.log(result); // Output: 'Adapted: Data from the legacy system'
Dans cet exemple, la classe Adapter encapsule OldSystem et fournit une nouvelle interface, newRequest, compatible avec votre application moderne.
Le modèle d’adaptateur est utile dans divers scénarios, notamment :
Bien que le modèle d'adaptateur offre flexibilité et compatibilité, il est essentiel de prendre en compte quelques points :
Le modèle Decorator est un modèle de conception structurelle qui vous permet d'ajouter dynamiquement de nouveaux comportements ou responsabilités aux objets sans modifier leur code existant. C'est un moyen puissant d'étendre la fonctionnalité des objets en les enveloppant avec des objets décoratifs. Ce modèle promeut le principe « ouvert à l’extension, mais fermé à la modification », ce qui facilite l’ajout de nouvelles fonctionnalités aux objets sans modifier leur implémentation principale.
En JavaScript, le modèle Decorator peut être implémenté à l'aide de classes et de compositions d'objets. Explorons comment implémenter et utiliser le modèle Decorator en JavaScript avec un exemple pratique.
Supposons que vous ayez une classe de base Coffee
:
class Coffee { cost() { return 5; // Base cost of a regular coffee } } Now, you want to add decorators to your coffee to customize it with additional options, such as milk and sugar:
javascript Copy code class MilkDecorator { constructor(coffee) { this.coffee = coffee; } cost() { return this.coffee.cost() + 2; // Adding the cost of milk } }class SugarDecorator { constructor(coffee) { this.coffee = coffee; } cost() { return this.coffee.cost() + 1; // Adding the cost of sugar } }
Vous pouvez ensuite créer des instances de café décorées comme ceci :
const regularCoffee = new Coffee(); const coffeeWithMilk = new MilkDecorator(regularCoffee); const coffeeWithMilkAndSugar = new SugarDecorator(coffeeWithMilk);
console.log(regularCoffee.cost()); // Output: 5 console.log(coffeeWithMilk.cost()); // Output: 7 console.log(coffeeWithMilkAndSugar.cost()); // Output: 8
Dans cet exemple, nous avons la classe Coffee représentant un café de base. Les classes MilkDecorator et SugarDecorator sont des décorateurs qui enveloppent un objet à café et ajoutent respectivement le coût du lait et du sucre au coût de base.
Le motif Décorateur est utile dans divers scénarios, notamment :
Bien que le motif décoratif soit polyvalent, il est important de garder quelques considérations à l'esprit :
Le modèle de proxy est un modèle de conception structurelle qui fournit un substitut ou un espace réservé à un autre objet pour en contrôler l'accès. Il agit comme un intermédiaire ou un wrapper autour de l'objet cible, vous permettant d'ajouter des comportements supplémentaires, de contrôler l'accès ou de retarder la création d'un objet. Le modèle de proxy est utile dans divers scénarios, tels que la mise en œuvre du chargement différé, du contrôle d'accès et de la journalisation.
En JavaScript, des proxys peuvent être créés à l'aide de l'objet Proxy
intégré. Explorons comment implémenter et utiliser le modèle de proxy en JavaScript avec des exemples pratiques.
Supposons que vous disposiez d'un objet gourmand en ressources que vous souhaitez charger paresseusement uniquement lorsque cela est nécessaire. Vous pouvez utiliser un proxy pour réaliser un chargement paresseux :
class ExpensiveResource { constructor() { console.log('Creating an expensive resource...'); }
fetchData() { console.log('Fetching data...'); } }class LazyResourceProxy { constructor() { this.resource = null; } fetchData() { if (!this.resource) { this.resource = new ExpensiveResource(); } this.resource.fetchData(); } }// Usage const lazyResource = new LazyResourceProxy(); // The actual resource is created and data is fetched only when needed lazyResource.fetchData();
Dans cet exemple, LazyResourceProxy agit comme un substitut pour ExpensiveResource, créant la ressource réelle uniquement lorsque la méthode fetchData est appelée pour la première fois.
Vous pouvez également utiliser des proxys pour contrôler l'accès aux objets et à leurs propriétés :
const user = { username: 'john_doe', password: 'secret123', };
const userProxy = new Proxy(user, { get(target, property) { if (property === 'password') { throw new Error('Access denied to password.'); } return target[property]; }, });console.log(userProxy.username); // Output: 'john_doe' console.log(userProxy.password); // Throws an error: 'Access denied to password.'
Dans cet exemple, le proxy intercepte l'opération get et restreint l'accès à la propriété password.
Le modèle proxy est utile dans divers scénarios, notamment :
Lorsque vous utilisez le modèle de proxy, gardez les considérations suivantes à l'esprit :
Le modèle composite est un modèle de conception structurelle qui vous permet de composer des objets dans des structures arborescentes pour représenter des hiérarchies partie-tout. Il permet aux clients de traiter des objets individuels et des compositions d'objets de manière uniforme. Le modèle composite est particulièrement utile lorsque vous devez travailler avec des structures complexes composées d'objets plus petits et liés tout en conservant une interface cohérente.
En JavaScript, vous pouvez implémenter le modèle composite à l'aide de classes ou d'objets partageant une interface commune, vous permettant de créer des structures hiérarchiques. Explorons comment implémenter et utiliser le modèle composite en JavaScript avec des exemples pratiques.
Supposons que vous construisiez une application de conception graphique devant fonctionner à la fois avec des formes simples et des compositions de formes complexes (par exemple, des groupes). Vous pouvez utiliser le modèle composite pour représenter cette hiérarchie :
// Component interface class Graphic { draw() {} }
// Leaf class (represents simple shapes) class Circle extends Graphic { constructor() { super(); // Circle-specific properties and methods } draw() { // Draw a circle } }// Composite class (represents groups of shapes) class Group extends Graphic { constructor() { super(); this.graphics = []; } add(graphic) { this.graphics.push(graphic); } draw() { // Draw each graphic in the group this.graphics.forEach((graphic) => graphic.draw()); } }// Usage const circle1 = new Circle(); const circle2 = new Circle(); const group = new Group();group.add(circle1); group.add(circle2);group.draw(); // Draws both circles in the group
Dans cet exemple, la classe Graphic sert d'interface de composant. La classe Circle représente des formes simples, tandis que la classe Group représente des compositions de formes. Les classes Circle et Group implémentent la méthode draw, vous permettant de les traiter uniformément lors du rendu.
Le modèle composite est utile dans divers scénarios, notamment :
Lorsque vous travaillez avec le modèle composite, tenez compte des éléments suivants :
Le Bridge Pattern est un modèle de conception structurelle qui sépare l'abstraction d'un objet de sa mise en œuvre. Il permet de créer un pont entre les deux, leur permettant de varier indépendamment. Ce modèle est particulièrement utile lorsque vous souhaitez éviter une liaison permanente entre une abstraction et son implémentation, rendant ainsi votre code plus flexible et plus maintenable.
En JavaScript, le Bridge Pattern peut être implémenté à l'aide de classes et d'objets qui fournissent une interface abstraite pour l'abstraction et différentes implémentations concrètes pour diverses plates-formes ou fonctionnalités. Explorons comment implémenter et utiliser le Bridge Pattern en JavaScript avec des exemples pratiques.
Supposons que vous construisiez une application de dessin capable de restituer des formes sur différentes plates-formes, telles que les navigateurs Web et les appareils mobiles. Vous pouvez utiliser le Bridge Pattern pour séparer les formes de dessin (abstraction) de la logique de rendu (implémentation) :
// Abstraction class Shape { constructor(renderer) { this.renderer = renderer; }
draw() { // Delegating the drawing to the specific renderer this.renderer.renderShape(this); } }// Implementor interface class Renderer { renderShape(shape) {} }// Concrete Implementors class WebRenderer extends Renderer { renderShape(shape) { console.log(`Drawing on the web: ${shape.constructor.name}`); } }class MobileRenderer extends Renderer { renderShape(shape) { console.log(`Drawing on mobile: ${shape.constructor.name}`); } }// Concrete Abstractions (Shapes) class Circle extends Shape { constructor(renderer) { super(renderer); } }class Square extends Shape { constructor(renderer) { super(renderer); } }// Usage const webRenderer = new WebRenderer(); const mobileRenderer = new MobileRenderer();const circle = new Circle(webRenderer); const square = new Square(mobileRenderer);circle.draw(); // Output: Drawing on the web: Circle square.draw(); // Output: Drawing on mobile: Square
Dans cet exemple, la classe Shape représente l'abstraction (formes à dessiner) et la classe Renderer représente l'interface de l'implémenteur (logique de rendu spécifique à la plate-forme). Différents implémenteurs concrets (WebRenderer et MobileRenderer) fournissent respectivement une logique de rendu pour les plates-formes Web et mobiles. Les classes Circle et Square sont des abstractions concrètes représentant des formes.
Le modèle de pont est utile dans divers scénarios, notamment :
Lorsque vous utilisez le modèle de pont, tenez compte des éléments suivants :
Le Flyweight Pattern est un modèle de conception structurelle qui vise à réduire la consommation de mémoire et à améliorer les performances en partageant les parties communes des objets. Il y parvient en séparant l'état intrinsèque d'un objet (partagé et immuable) de son état extrinsèque (unique et dépendant du contexte). Ce modèle est particulièrement utile lorsque vous disposez d’un grand nombre d’objets similaires et que vous souhaitez minimiser l’empreinte mémoire.
En JavaScript, vous pouvez implémenter le modèle Flyweight à l'aide de classes ou d'objets pour représenter un état intrinsèque partagé et un état extrinsèque individuel. Explorons comment implémenter et utiliser le modèle Flyweight en JavaScript avec des exemples pratiques.
Supposons que vous développiez un éditeur de texte devant afficher une grande quantité de texte. Au lieu de créer un objet distinct pour chaque caractère, vous pouvez utiliser le modèle Flyweight pour partager des objets de caractère lorsqu'ils ont les mêmes propriétés intrinsèques (par exemple, police et taille) :
class Character { constructor(char, font, size) { this.char = char; this.font = font; this.size = size; }
render() { console.log(`Rendering character "${this.char}" in ${this.font}, size ${this.size}`); } }class CharacterFactory { constructor() { this.characters = {}; } getCharacter(char, font, size) { const key = `${char}-${font}-${size}`; if (!this.characters[key]) { this.characters[key] = new Character(char, font, size); } return this.characters[key]; } }// Usage const factory = new CharacterFactory();const charA1 = factory.getCharacter('A', 'Arial', 12); const charA2 = factory.getCharacter('A', 'Arial', 12); const charB = factory.getCharacter('B', 'Times New Roman', 14);charA1.render(); // Output: Rendering character "A" in Arial, size 12 charA2.render(); // Output: Rendering character "A" in Arial, size 12 (shared instance) charB.render(); // Output: Rendering character "B" in Times New Roman, size 14
Dans cet exemple, la classe Character représente des caractères individuels avec des propriétés intrinsèques telles que le caractère lui-même, la police et la taille. La classe CharacterFactory garantit que les caractères ayant les mêmes propriétés intrinsèques sont partagés plutôt que dupliqués.
Le modèle Flyweight est utile dans divers scénarios, notamment :
Lorsque vous utilisez le modèle Flyweight, tenez compte des éléments suivants :
Le modèle d'observateur est un modèle de conception comportemental qui établit une dépendance un-à-plusieurs entre les objets. Il permet à un objet (le sujet ou l'observable) d'informer plusieurs observateurs (auditeurs) des changements dans son état ou ses données. Ce modèle est couramment utilisé pour implémenter des systèmes de gestion d'événements distribués, dans lesquels les changements d'état d'un objet déclenchent des actions dans d'autres objets dépendants.
En JavaScript, vous pouvez implémenter le modèle Observer à l'aide de classes personnalisées ou de fonctionnalités intégrées telles que les écouteurs d'événements et la méthode addEventListener
. Explorons comment implémenter et utiliser le modèle Observer en JavaScript avec des exemples pratiques.
Supposons que vous créez une application météo et que vous souhaitiez que différentes parties de l'interface utilisateur soient mises à jour lorsque les conditions météorologiques changent. Vous pouvez utiliser une implémentation personnalisée du modèle Observer :
class WeatherStation { constructor() { this.observers = []; }
addObserver(observer) { this.observers.push(observer); } removeObserver(observer) { const index = this.observers.indexOf(observer); if (index !== -1) { this.observers.splice(index, 1); } } notifyObservers() { this.observers.forEach((observer) => { observer.update(this); }); } setWeatherData(weatherData) { this.weatherData = weatherData; this.notifyObservers(); } }class WeatherDisplay { update(weatherStation) { console.log(`Current weather: ${weatherStation.weatherData}`); } }// Usage const weatherStation = new WeatherStation(); const display1 = new WeatherDisplay(); const display2 = new WeatherDisplay();weatherStation.addObserver(display1); weatherStation.addObserver(display2);weatherStation.setWeatherData('Sunny'); // Both displays update with the new weather data
Dans cet exemple, la WeatherStation agit comme le sujet qui avertit les observateurs (objets d'affichage) lorsque les données météorologiques changent. Les observateurs s'abonnent au sujet à l'aide de la méthode addObserver et implémentent la méthode update pour réagir aux changements.
JavaScript fournit également un moyen intégré d'implémenter le modèle Observer à l'aide d'écouteurs d'événements :
class NewsPublisher { constructor() { this.subscribers = []; }
subscribe(subscriber) { this.subscribers.push(subscriber); } unsubscribe(subscriber) { const index = this.subscribers.indexOf(subscriber); if (index !== -1) { this.subscribers.splice(index, 1); } } publishNews(news) { this.subscribers.forEach((subscriber) => { subscriber(news); }); } }// Usage const publisher = new NewsPublisher();const subscriber1 = (news) => { console.log(`Subscriber 1 received news: ${news}`); };const subscriber2 = (news) => { console.log(`Subscriber 2 received news: ${news}`); };publisher.subscribe(subscriber1); publisher.subscribe(subscriber2);publisher.publishNews('Breaking News: Important Announcement');
Dans cet exemple, NewsPublisher fait office de sujet et les abonnés (fonctions) sont ajoutés à l'aide de la méthode d'abonnement. La méthode submitNews informe les abonnés en appelant leurs fonctions avec les actualités.
Le modèle Observer est utile dans divers scénarios, notamment :
Lorsque vous utilisez le modèle d'observateur, tenez compte des éléments suivants :
Le modèle de stratégie est un modèle de conception comportemental qui vous permet de définir une famille d'algorithmes interchangeables, d'encapsuler chacun d'eux et de les rendre interchangeables. Il permet aux clients de choisir dynamiquement l'algorithme approprié au moment de l'exécution. Ce modèle favorise la flexibilité et la réutilisabilité en séparant le comportement de l'algorithme du contexte qui l'utilise.
En JavaScript, vous pouvez implémenter le modèle de stratégie à l'aide d'objets ou de fonctions pour représenter différentes stratégies et d'un objet contextuel pouvant basculer entre ces stratégies. Explorons comment implémenter et utiliser le modèle de stratégie en JavaScript avec des exemples pratiques.
Supposons que vous développiez une application de commerce électronique et que vous souhaitiez calculer des remises pour différents types de clients. Vous pouvez utiliser le modèle de stratégie pour encapsuler des stratégies de remise :
// Discount Strategies const regularCustomerDiscount = (amount) => amount * 0.1; // 10% discount const premiumCustomerDiscount = (amount) => amount * 0.2; // 20% discount
// Context class ShoppingCart { constructor(discountStrategy) { this.items = []; this.discountStrategy = discountStrategy; } addItem(item) { this.items.push(item); } calculateTotal() { const subtotal = this.items.reduce((total, item) => total + item.price, 0); return subtotal - this.discountStrategy(subtotal); } }// Usage const regularCustomerCart = new ShoppingCart(regularCustomerDiscount); const premiumCustomerCart = new ShoppingCart(premiumCustomerDiscount);regularCustomerCart.addItem({ name: 'Item 1', price: 50 }); premiumCustomerCart.addItem({ name: 'Item 2', price: 100 });console.log(`Regular Customer Total: $${regularCustomerCart.calculateTotal()}`); // Output: $45 (after 10% discount) console.log(`Premium Customer Total: $${premiumCustomerCart.calculateTotal()}`); // Output: $80 (after 20% discount)
Dans cet exemple, nous définissons deux stratégies de remise en tant que fonctions (regularCustomerDiscount et premiumCustomerDiscount). La classe ShoppingCart prend comme paramètre une stratégie de remise et calcule le prix total en fonction de la stratégie choisie.
Le modèle de stratégie est utile dans divers scénarios, notamment :
Lorsque vous utilisez le modèle de stratégie, tenez compte des éléments suivants :
Le modèle de commande est un modèle de conception comportemental qui transforme une requête ou une opération simple en un objet autonome. Il vous permet de paramétrer des objets avec différentes requêtes, de retarder ou de mettre en file d'attente l'exécution d'une requête et de prendre en charge les opérations annulables. Ce modèle dissocie l'expéditeur d'une requête de son destinataire, ce qui facilite l'extension et la maintenance du code.
En JavaScript, vous pouvez implémenter le modèle de commande à l'aide d'objets ou de classes pour représenter les commandes et les invocateurs qui exécutent ces commandes. Explorons comment implémenter et utiliser le modèle de commande en JavaScript avec des exemples pratiques.
Supposons que vous développiez une application de contrôle à distance pour une maison intelligente et que vous souhaitiez créer un moyen flexible de contrôler divers appareils.
Vous pouvez utiliser le modèle de commande :
// Command interface class Command { execute() {} }
// Concrete Commands class LightOnCommand extends Command { constructor(light) { super(); this.light = light; } execute() { this.light.turnOn(); } }class LightOffCommand extends Command { constructor(light) { super(); this.light = light; } execute() { this.light.turnOff(); } }// Receiver (Device) class Light { turnOn() { console.log('Light is on.'); } turnOff() { console.log('Light is off.'); } }// Invoker (Remote Control) class RemoteControl { constructor() { this.commands = []; } addCommand(command) { this.commands.push(command); } executeCommands() { this.commands.forEach((command) => { command.execute(); }); } }// Usage const livingRoomLight = new Light(); const kitchenLight = new Light();const livingRoomLightOn = new LightOnCommand(livingRoomLight); const livingRoomLightOff = new LightOffCommand(livingRoomLight); const kitchenLightOn = new LightOnCommand(kitchenLight); const kitchenLightOff = new LightOffCommand(kitchenLight);const remoteControl = new RemoteControl();remoteControl.addCommand(livingRoomLightOn); remoteControl.addCommand(kitchenLightOff);remoteControl.executeCommands(); // Output: "Light is on." (for living room) // Output: "Light is off." (for kitchen)
Dans cet exemple, le modèle de commande est utilisé pour encapsuler les actions d'allumage et d'extinction des lumières. Le RemoteControl sert d'invocateur et des commandes concrètes (par exemple, LightOnCommand et LightOffCommand) encapsulent les actions à exécuter.
Le modèle de commande est utile dans divers scénarios, notamment :
Lorsque vous utilisez le modèle de commande, tenez compte des éléments suivants :
Le modèle d'état est un modèle de conception comportemental qui permet à un objet de modifier son comportement lorsque son état interne change. Il encapsule les états en classes distinctes et délègue le comportement à l'objet d'état actuel. Ce modèle permet de gérer des transitions d'état complexes et favorise le principe « ouvert-fermé », facilitant l'ajout de nouveaux états sans modifier le code existant.
En JavaScript, vous pouvez implémenter le modèle d'état à l'aide de classes pour représenter les états et d'un objet contextuel qui délègue son comportement à l'état actuel. Explorons comment implémenter et utiliser le modèle d'état en JavaScript avec des exemples pratiques.
Supposons que vous développiez un distributeur automatique qui distribue différents produits. Le comportement du distributeur automatique dépend de son état actuel, par exemple « Prêt », « Distribution » ou « Épuisé ». Vous pouvez utiliser le modèle d'état pour modéliser ce comportement :
// State interface class VendingMachineState { insertMoney() {} ejectMoney() {} selectProduct() {} dispenseProduct() {} }
// Concrete States class ReadyState extends VendingMachineState { constructor(machine) { super(); this.machine = machine; } insertMoney() { console.log('Money inserted.'); this.machine.setState(this.machine.getDispensingState()); } selectProduct() { console.log('Please insert money first.'); } }class DispensingState extends VendingMachineState { constructor(machine) { super(); this.machine = machine; } dispenseProduct() { console.log('Product dispensed.'); this.machine.setState(this.machine.getReadyState()); } }class VendingMachine { constructor() { this.readyState = new ReadyState(this); this.dispensingState = new DispensingState(this); this.currentState = this.readyState; } setState(state) { this.currentState = state; } getReadyState() { return this.readyState; } getDispensingState() { return this.dispensingState; } insertMoney() { this.currentState.insertMoney(); } selectProduct() { this.currentState.selectProduct(); } dispenseProduct() { this.currentState.dispenseProduct(); } }// Usage const vendingMachine = new VendingMachine();vendingMachine.selectProduct(); // Output: "Please insert money first." vendingMachine.insertMoney(); // Output: "Money inserted." vendingMachine.dispenseProduct(); // Output: "Product dispensed."
Dans cet exemple, le State Pattern est utilisé pour gérer le comportement d'un distributeur automatique. Les états comme « Prêt » et « Distribution » sont représentés comme des classes distinctes, et le contexte (distributeur automatique) délègue son comportement à l'état actuel.
Le modèle d'état est utile dans divers scénarios, notamment :
Lorsque vous utilisez le modèle d'état, tenez compte des éléments suivants :
Le modèle de chaîne de responsabilité est un modèle de conception comportemental qui vous aide à créer une chaîne d'objets pour gérer une demande. Chaque objet de la chaîne a la possibilité de traiter la demande ou de la transmettre à l'objet suivant de la chaîne. Il dissocie l'expéditeur d'une requête de ses destinataires et permet à plusieurs gestionnaires d'être dans la chaîne. Ce modèle favorise la flexibilité et l'extensibilité en vous permettant d'ajouter ou de modifier des gestionnaires sans affecter le code client.
En JavaScript, vous pouvez implémenter le modèle de chaîne de responsabilité à l'aide d'objets ou de classes qui représentent des gestionnaires et un client qui initie les requêtes. Chaque gestionnaire a une référence au gestionnaire suivant dans la chaîne. Explorons comment implémenter et utiliser le modèle de chaîne de responsabilité en JavaScript avec des exemples pratiques.
Supposons que vous développiez un système de traitement des commandes et que vous souhaitiez traiter les commandes en fonction de leur montant total. Vous pouvez utiliser le modèle de chaîne de responsabilité pour créer une chaîne de gestionnaires, chacun étant responsable du traitement des commandes dans une certaine fourchette de prix :
// Handler interface class OrderHandler { constructor() { this.nextHandler = null; }
setNextHandler(handler) { this.nextHandler = handler; } handleOrder(order) { if (this.canHandleOrder(order)) { this.processOrder(order); } else if (this.nextHandler) { this.nextHandler.handleOrder(order); } else { console.log('No handler can process this order.'); } } canHandleOrder(order) {} processOrder(order) {} }// Concrete Handlers class SmallOrderHandler extends OrderHandler { canHandleOrder(order) { return order.amount <= 100; } processOrder(order) { console.log(`Processing small order for ${order.amount}`); } }class MediumOrderHandler extends OrderHandler { canHandleOrder(order) { return order.amount <= 500; } processOrder(order) { console.log(`Processing medium order for ${order.amount}`); } }class LargeOrderHandler extends OrderHandler { canHandleOrder(order) { return order.amount > 500; } processOrder(order) { console.log(`Processing large order for ${order.amount}`); } }// Client class Order { constructor(amount) { this.amount = amount; } }// Usage const smallOrderHandler = new SmallOrderHandler(); const mediumOrderHandler = new MediumOrderHandler(); const largeOrderHandler = new LargeOrderHandler();smallOrderHandler.setNextHandler(mediumOrderHandler); mediumOrderHandler.setNextHandler(largeOrderHandler);const order1 = new Order(80); const order2 = new Order(250); const order3 = new Order(600);smallOrderHandler.handleOrder(order1); // Output: "Processing small order for 80" smallOrderHandler.handleOrder(order2); // Output: "Processing medium order for 250" smallOrderHandler.handleOrder(order3); // Output: "Processing large order for 600"
Dans cet exemple, le modèle de chaîne de responsabilité est utilisé pour gérer des commandes de montants différents. Les gestionnaires tels que SmallOrderHandler, MediumOrderHandler et LargeOrderHandler déterminent chacun s'ils peuvent traiter une commande en fonction du montant de la commande. S’ils le peuvent, ils le traitent ; sinon, ils transmettent la commande au prochain gestionnaire de la chaîne.
Le modèle de chaîne de responsabilité est utile dans divers scénarios, notamment :
Lorsque vous utilisez le modèle de chaîne de responsabilité, tenez compte des éléments suivants :
Le modèle de visiteur est un modèle de conception comportemental qui vous permet de séparer un algorithme de la structure d'objet sur laquelle il opère. Il fournit un moyen d'ajouter de nouvelles opérations aux objets sans modifier leurs classes, ce qui facilite l'extension des fonctionnalités pour les hiérarchies d'objets complexes. Ce modèle est particulièrement utile lorsque vous disposez d’un ensemble d’éléments distincts et que vous souhaitez effectuer diverses opérations sur ceux-ci sans modifier leur code.
En JavaScript, vous pouvez implémenter le modèle de visiteur à l'aide de fonctions ou de classes pour représenter les visiteurs qui visitent des éléments au sein d'une structure d'objet. Explorons comment implémenter et utiliser le modèle de visiteur en JavaScript avec des exemples pratiques.
Supposons que vous développiez un système de gestion de contenu dans lequel vous disposez de différents types d'éléments de contenu tels que des articles, des images et des vidéos. Vous souhaitez effectuer diverses opérations, telles que le rendu et l'exportation, sur ces éléments sans modifier leurs classes. Vous pouvez utiliser le modèle de visiteur :
// Element interface class ContentElement { accept(visitor) {} }
// Concrete Elements class Article extends ContentElement { accept(visitor) { visitor.visitArticle(this); } }class Image extends ContentElement { accept(visitor) { visitor.visitImage(this); } }class Video extends ContentElement { accept(visitor) { visitor.visitVideo(this); } }// Visitor interface class Visitor { visitArticle(article) {} visitImage(image) {} visitVideo(video) {} }// Concrete Visitors class RendererVisitor extends Visitor { visitArticle(article) { console.log(`Rendering article: ${article.title}`); } visitImage(image) { console.log(`Rendering image: ${image.caption}`); } visitVideo(video) { console.log(`Rendering video: ${video.title}`); } }class ExportVisitor extends Visitor { visitArticle(article) { console.log(`Exporting article: ${article.title}`); } visitImage(image) { console.log(`Exporting image: ${image.caption}`); } visitVideo(video) { console.log(`Exporting video: ${video.title}`); } }// Usage const elements = [new Article('Article 1'), new Image('Image 1'), new Video('Video 1')]; const renderer = new RendererVisitor(); const exporter = new ExportVisitor();elements.forEach((element) => { element.accept(renderer); element.accept(exporter); });
Dans cet exemple, nous avons des éléments de contenu tels que Article, Image et Vidéo, et nous souhaitons effectuer des opérations de rendu et d'exportation sur eux sans modifier leurs classes. Nous y parvenons en implémentant des classes de visiteurs comme RendererVisitor et ExportVisitor qui visitent les éléments et effectuent les opérations souhaitées.
Le modèle de visiteur est utile dans divers scénarios, notamment :
Lorsque vous utilisez le modèle de visiteur, tenez compte des éléments suivants :
Dans cette exploration complète des modèles de conception en JavaScript, nous avons exploré divers modèles qui permettent aux développeurs de créer un code flexible, maintenable et efficace. Chaque modèle de conception répond à des problèmes spécifiques et fournit des solutions élégantes aux défis courants de conception de logiciels.
Nous avons commencé par comprendre le concept fondamental des modèles de conception et les avons classés en trois grands groupes : les modèles créationnels, structurels et comportementaux. Dans chaque catégorie, nous avons examiné les modèles de conception populaires et présenté leurs implémentations pratiques en JavaScript.
Voici un bref récapitulatif des principaux modèles de conception que nous avons abordés :
Modèles de création : ces modèles se concentrent sur les mécanismes de création d'objets, notamment le modèle Singleton pour garantir une instance unique d'une classe, les modèles d'usine et d'usine abstraite pour créer des objets avec des usines flexibles, le modèle Builder pour construire des objets complexes étape par étape, le modèle de prototype pour le clonage. objets et Object Pool Pattern pour une réutilisation efficace des objets.
Modèles structurels : ces modèles traitent de la composition des objets, offrant des moyens de construire des structures complexes à partir de composants plus simples. Nous avons exploré le modèle Adapter pour adapter les interfaces, le modèle Décorateur pour ajouter dynamiquement un comportement aux objets, le modèle Proxy pour contrôler l'accès aux objets, le modèle Composite pour composer des objets en structures arborescentes, le modèle Bridge pour séparer l'abstraction de la mise en œuvre et le Flyweight. Modèle permettant de minimiser l'utilisation de la mémoire en partageant l'état commun.
Modèles comportementaux : ces modèles concernent l’interaction et la communication entre les objets. Nous avons couvert le modèle d'observateur pour la mise en œuvre de systèmes de gestion d'événements distribués, le modèle de stratégie pour encapsuler des algorithmes interchangeables, le modèle de commande pour transformer les requêtes en objets autonomes, le modèle d'état pour gérer le comportement des objets en fonction de l'état interne, le modèle de chaîne de responsabilité pour construire un chaîne de gestionnaires pour le traitement des demandes et le modèle de visiteur pour séparer les algorithmes des structures d'objets.
Les modèles de conception sont des outils précieux dans la boîte à outils d'un développeur, permettant la création de bases de code évolutives et maintenables. Comprendre et appliquer ces modèles dans vos projets JavaScript vous permet d'écrire des logiciels plus efficaces, adaptables et robustes.
N'oubliez pas que les modèles de conception ne sont pas des solutions universelles et que leur applicabilité dépend des exigences et des défis spécifiques de votre projet. Réfléchissez soigneusement quand et comment les appliquer pour obtenir les meilleurs résultats.
Au fur et à mesure que vous progressez en tant que développeur JavaScript, la maîtrise de ces modèles de conception vous permettra de relever des défis complexes de conception de logiciels avec confiance et créativité. Que vous créiez des applications Web, des moteurs de jeu ou tout autre logiciel, les modèles de conception seront vos alliés pour créer un code élégant et maintenable. Bon codage !
Également publié ici .