POO
Classes et Objets Modernes
JavaScript Moderne : Programmation Orientée Objet
JavaScript est un langage flexible qui supporte plusieurs paradigmes de programmation, dont la programmation orientée objet (POO). Depuis ES6, JavaScript a introduit une syntaxe de classe plus intuitive, même si le modèle sous-jacent reste basé sur les prototypes. Dans ce tutoriel, nous explorerons la POO en JavaScript moderne, y compris les classes, l’héritage, les mixins et les patterns de conception couramment utilisés.
Le modèle objet en JavaScript
Avant d’explorer la syntaxe moderne des classes, il est important de comprendre comment fonctionne le modèle objet en JavaScript.
Objets et prototypes
En JavaScript, presque tout est un objet, et l’héritage fonctionne via des prototypes :
// Création d'un objet littéral
const personne = {
nom: "Alice",
saluer() {
return `Bonjour, je suis ${this.nom}`;
}
};
console.log(personne.saluer()); // "Bonjour, je suis Alice"
// Création d'un nouvel objet basé sur le prototype de personne
const etudiant = Object.create(personne);
etudiant.nom = "Bob";
etudiant.niveau = "Master";
console.log(etudiant.saluer()); // "Bonjour, je suis Bob"
console.log(etudiant.niveau); // "Master"
Fonctions constructeur (ancienne méthode)
Avant ES6, la création d’objets similaires se faisait généralement à l’aide de fonctions constructeur :
// Fonction constructeur
function Personne(nom, age) {
this.nom = nom;
this.age = age;
}
// Méthode ajoutée au prototype
Personne.prototype.saluer = function() {
return `Bonjour, je suis ${this.nom} et j'ai ${this.age} ans`;
};
// Création d'instances
const alice = new Personne("Alice", 30);
const bob = new Personne("Bob", 25);
console.log(alice.saluer()); // "Bonjour, je suis Alice et j'ai 30 ans"
console.log(bob.saluer()); // "Bonjour, je suis Bob et j'ai 25 ans"
Classes en JavaScript moderne (ES6+)
ES6 a introduit une syntaxe de classe plus intuitive qui simplifie la création d’objets et l’implémentation de l’héritage.
Définition de classe
class Personne {
// Constructeur
constructor(nom, age) {
this.nom = nom;
this.age = age;
}
// Méthodes
saluer() {
return `Bonjour, je suis ${this.nom} et j'ai ${this.age} ans`;
}
decrire() {
return `${this.nom} est une personne de ${this.age} ans`;
}
}
// Création d'instances
const alice = new Personne("Alice", 30);
console.log(alice.saluer()); // "Bonjour, je suis Alice et j'ai 30 ans"
Propriétés et méthodes statiques
Les méthodes et propriétés statiques appartiennent à la classe elle-même, pas aux instances :
class MathUtils {
// Propriété statique (ES2022+)
static PI = 3.14159;
// Méthode statique
static carre(x) {
return x * x;
}
static aire_cercle(rayon) {
return MathUtils.PI * MathUtils.carre(rayon);
}
}
console.log(MathUtils.PI); // 3.14159
console.log(MathUtils.carre(4)); // 16
console.log(MathUtils.aire_cercle(2)); // 12.56636
// Les propriétés et méthodes statiques ne sont pas accessibles sur les instances
const utils = new MathUtils();
// console.log(utils.PI); // undefined
Accesseurs (getters et setters)
Les accesseurs permettent de contrôler l’accès aux propriétés d’une classe :
class Compte {
constructor(proprietaire) {
this.proprietaire = proprietaire;
this._solde = 0; // Convention : _ indique une propriété "privée"
}
// Getter
get solde() {
return `${this._solde} €`;
}
// Setter
set solde(valeur) {
if (isNaN(valeur)) {
throw new Error("Le solde doit être un nombre");
}
this._solde = valeur;
}
// Méthodes normales
deposer(montant) {
this.solde = this._solde + montant;
return this._solde;
}
retirer(montant) {
if (montant > this._solde) {
throw new Error("Fonds insuffisants");
}
this.solde = this._solde - montant;
return this._solde;
}
}
const compte = new Compte("Alice");
compte.deposer(1000);
console.log(compte.solde); // "1000 €"
compte.retirer(500);
console.log(compte.solde); // "500 €"
// compte.solde = "beaucoup"; // Erreur: Le solde doit être un nombre
Champs de classe (Class Fields)
Les champs de classe, introduits dans les versions plus récentes de JavaScript, permettent de déclarer des propriétés directement dans la classe :
class Produit {
// Champs publics
nom;
prix;
// Champ privé (ECMAScript 2022+)
#stock = 0;
// Champ statique
static nbProduits = 0;
constructor(nom, prix, stock) {
this.nom = nom;
this.prix = prix;
this.#stock = stock;
Produit.nbProduits++;
}
get stock() {
return this.#stock;
}
set stock(valeur) {
if (valeur < 0) {
throw new Error("Le stock ne peut pas être négatif");
}
this.#stock = valeur;
}
vendre(quantite) {
if (quantite > this.#stock) {
throw new Error("Stock insuffisant");
}
this.#stock -= quantite;
return `${quantite} unité(s) de ${this.nom} vendue(s)`;
}
// Méthode privée (ECMAScript 2022+)
#miseAJourInterne() {
console.log("Mise à jour interne");
}
}
const p1 = new Produit("Téléphone", 499, 10);
console.log(p1.nom); // "Téléphone"
console.log(p1.stock); // 10
p1.stock = 15;
console.log(p1.stock); // 15
console.log(p1.vendre(3)); // "3 unité(s) de Téléphone vendue(s)"
console.log(p1.stock); // 12
// console.log(p1.#stock); // Erreur: propriété privée
// p1.#miseAJourInterne(); // Erreur: méthode privée
console.log(Produit.nbProduits); // 1
Héritage et polymorphisme
L’héritage permet à une classe d’hériter des propriétés et méthodes d’une autre classe.
Héritage avec extends
class Animal {
constructor(nom) {
this.nom = nom;
}
parler() {
return `${this.nom} fait du bruit`;
}
}
class Chien extends Animal {
constructor(nom, race) {
super(nom); // Appel du constructeur parent
this.race = race;
}
// Surcharge de la méthode parler
parler() {
return `${this.nom} aboie`;
}
decrire() {
return `${this.nom} est un chien de race ${this.race}`;
}
}
const animal = new Animal("Animal inconnu");
console.log(animal.parler()); // "Animal inconnu fait du bruit"
const rex = new Chien("Rex", "Berger Allemand");
console.log(rex.parler()); // "Rex aboie"
console.log(rex.decrire()); // "Rex est un chien de race Berger Allemand"
La chaîne de prototypes
Même avec la syntaxe moderne des classes, JavaScript utilise toujours des prototypes en coulisses :
const rex = new Chien("Rex", "Berger Allemand");
console.log(rex instanceof Chien); // true
console.log(rex instanceof Animal); // true
console.log(rex instanceof Object); // true
console.log(Object.getPrototypeOf(rex) === Chien.prototype); // true
console.log(Object.getPrototypeOf(Chien.prototype) === Animal.prototype); // true
Contrôle de type
Pour vérifier si un objet est une instance d’une classe, utilisez l’opérateur instanceof
:
function traiterAnimal(animal) {
if (animal instanceof Chien) {
console.log(animal.parler()); // Méthode de Chien
} else if (animal instanceof Animal) {
console.log(animal.parler()); // Méthode d'Animal
} else {
console.log("Ceci n'est pas un animal");
}
}
traiterAnimal(new Chien("Rex", "Berger Allemand")); // "Rex aboie"
traiterAnimal(new Animal("Lion")); // "Lion fait du bruit"
traiterAnimal({}); // "Ceci n'est pas un animal"
Composition et mixins
Parfois, l’héritage seul n’est pas suffisant pour modéliser des relations complexes. La composition et les mixins offrent des alternatives flexibles.
Composition
La composition consiste à combiner des objets plus simples pour créer des objets plus complexes :
// Objets de capacités
const peutNager = {
nager() {
return `${this.nom} nage`;
}
};
const peutVoler = {
voler() {
return `${this.nom} vole`;
}
};
// Classe de base
class Animal {
constructor(nom) {
this.nom = nom;
}
manger() {
return `${this.nom} mange`;
}
}
// Composition par mixin
class Canard extends Animal {
constructor(nom) {
super(nom);
// Composition: ajouter des capacités
Object.assign(this, peutNager, peutVoler);
}
}
class Poisson extends Animal {
constructor(nom) {
super(nom);
// Composition: seulement nager
Object.assign(this, peutNager);
}
}
const donald = new Canard("Donald");
console.log(donald.manger()); // "Donald mange"
console.log(donald.nager()); // "Donald nage"
console.log(donald.voler()); // "Donald vole"
const nemo = new Poisson("Nemo");
console.log(nemo.nager()); // "Nemo nage"
// console.log(nemo.voler()); // Erreur: nemo.voler n'est pas une fonction
Mixins fonctionnels
Les mixins fonctionnels sont des fonctions qui ajoutent des propriétés et des méthodes à un objet existant :
// Mixin fonctionnel
function avecTimestamp(Base) {
return class extends Base {
constructor(...args) {
super(...args);
this.createdAt = new Date();
}
getAge() {
return new Date() - this.createdAt;
}
};
}
function avecLogging(Base) {
return class extends Base {
log(message) {
console.log(`[${this.constructor.name}] ${message}`);
}
};
}
// Application des mixins
class Utilisateur {
constructor(nom) {
this.nom = nom;
}
}
// Combinaison de mixins
const UtilisateurAvecExtensions = avecLogging(avecTimestamp(Utilisateur));
const user = new UtilisateurAvecExtensions("Alice");
console.log(user.nom); // "Alice"
console.log(user.createdAt); // Date de création
user.log("Instance créée"); // "[UtilisateurAvecExtensions] Instance créée"
Patterns de conception en JavaScript
Les patterns de conception sont des solutions réutilisables à des problèmes récurrents. Voici quelques patterns courants en JavaScript.
Singleton
Le pattern Singleton garantit qu’une classe n’a qu’une seule instance et fournit un point d’accès global à cette instance :
class Configuration {
constructor() {
if (Configuration._instance) {
return Configuration._instance;
}
this.theme = "clair";
this.langue = "fr";
this.notifications = true;
Configuration._instance = this;
}
static getInstance() {
return new Configuration();
}
}
const config1 = Configuration.getInstance();
const config2 = Configuration.getInstance();
console.log(config1 === config2); // true
config1.theme = "sombre";
console.log(config2.theme); // "sombre" (même instance)
Factory (Fabrique)
Le pattern Factory crée des objets sans exposer la logique de création :
// Classes concrètes
class VoitureEconomique {
constructor(options) {
this.portes = options.portes || 4;
this.couleur = options.couleur || "blanche";
this.consommation = "faible";
}
}
class VoitureLuxe {
constructor(options) {
this.portes = options.portes || 2;
this.couleur = options.couleur || "noire";
this.consommation = "élevée";
this.options = ["cuir", "GPS", "toit ouvrant"];
}
}
class VoitureSport {
constructor(options) {
this.portes = 2;
this.couleur = options.couleur || "rouge";
this.consommation = "très élevée";
this.vitesseMax = 300;
}
}
// Factory
class VoitureFactory {
createVoiture(type, options) {
switch(type) {
case "economique":
return new VoitureEconomique(options);
case "luxe":
return new VoitureLuxe(options);
case "sport":
return new VoitureSport(options);
default:
throw new Error(`Type de voiture non reconnu: ${type}`);
}
}
}
// Utilisation
const factory = new VoitureFactory();
const maVoiture = factory.createVoiture("luxe", { couleur: "bleu", portes: 4 });
console.log(maVoiture); // VoitureLuxe avec options personnalisées
Observer (Observateur)
Le pattern Observer permet à un objet de notifier d’autres objets des changements d’état :
class Sujet {
constructor() {
this.observateurs = [];
}
souscrire(observateur) {
this.observateurs.push(observateur);
}
desinscrire(observateur) {
this.observateurs = this.observateurs.filter(obs => obs !== observateur);
}
notifier(donnees) {
this.observateurs.forEach(observateur => observateur.update(donnees));
}
}
class Observable extends Sujet {
constructor() {
super();
this.etat = null;
}
getEtat() {
return this.etat;
}
setEtat(etat) {
this.etat = etat;
this.notifier(this);
}
}
// Exemple d'utilisation: système de notification météo
class StationMeteo extends Observable {
setMesures(temperature, humidite, pression) {
this.etat = { temperature, humidite, pression, date: new Date() };
this.notifier(this);
}
}
// Observateurs
class AffichageConditionsActuelles {
update(station) {
const { temperature, humidite } = station.getEtat();
console.log(`Conditions actuelles: ${temperature}°C et ${humidite}% d'humidité`);
}
}
class AffichageStatistiques {
constructor() {
this.temperatures = [];
}
update(station) {
const { temperature } = station.getEtat();
this.temperatures.push(temperature);
const max = Math.max(...this.temperatures);
const min = Math.min(...this.temperatures);
const moy = this.temperatures.reduce((acc, val) => acc + val, 0) / this.temperatures.length;
console.log(`Statistiques - Max: ${max}°C, Min: ${min}°C, Moyenne: ${moy.toFixed(1)}°C`);
}
}
// Utilisation
const station = new StationMeteo();
const affichageConditions = new AffichageConditionsActuelles();
const affichageStats = new AffichageStatistiques();
station.souscrire(affichageConditions);
station.souscrire(affichageStats);
// Simule des changements météo
station.setMesures(20, 65, 1013);
station.setMesures(22, 70, 1014);
station.setMesures(19, 75, 1012);
Module
Le pattern Module encapsule des fonctionnalités et expose une API publique tout en gardant certains détails privés :
// Module avec IIFE (ancien style)
const Compteur = (function() {
// Variables privées
let compte = 0;
// Méthodes privées
function valider(valeur) {
return typeof valeur === 'number' && !isNaN(valeur);
}
// API publique
return {
incrementer() {
return ++compte;
},
decrementer() {
return --compte;
},
ajouter(valeur) {
if (!valider(valeur)) {
throw new Error("Valeur invalide");
}
compte += valeur;
return compte;
},
getCompte() {
return compte;
}
};
})();
console.log(Compteur.getCompte()); // 0
Compteur.incrementer();
Compteur.incrementer();
console.log(Compteur.getCompte()); // 2
Compteur.ajouter(5);
console.log(Compteur.getCompte()); // 7
// console.log(Compteur.compte); // undefined (privé)
Décorateur
Le pattern Décorateur permet d’ajouter des comportements à des objets individuels sans affecter le comportement d’autres objets de la même classe :
// Composant de base
class Cafe {
cout() {
return 2.5;
}
description() {
return "Café simple";
}
}
// Décorateurs
class LaitDecorator {
constructor(cafe) {
this.cafe = cafe;
}
cout() {
return this.cafe.cout() + 0.5;
}
description() {
return `${this.cafe.description()} avec du lait`;
}
}
class SucreDecorator {
constructor(cafe) {
this.cafe = cafe;
}
cout() {
return this.cafe.cout() + 0.2;
}
description() {
return `${this.cafe.description()} avec du sucre`;
}
}
class CannelleDecorator {
constructor(cafe) {
this.cafe = cafe;
}
cout() {
return this.cafe.cout() + 0.3;
}
description() {
return `${this.cafe.description()} avec de la cannelle`;
}
}
// Utilisation
let monCafe = new Cafe();
console.log(`${monCafe.description()} : ${monCafe.cout()}€`);
// "Café simple : 2.5€"
// Ajout de lait
monCafe = new LaitDecorator(monCafe);
console.log(`${monCafe.description()} : ${monCafe.cout()}€`);
// "Café simple avec du lait : 3€"
// Ajout de sucre
monCafe = new SucreDecorator(monCafe);
console.log(`${monCafe.description()} : ${monCafe.cout()}€`);
// "Café simple avec du lait avec du sucre : 3.2€"
// Création d'un café personnalisé directement
const cafeComplet = new CannelleDecorator(new SucreDecorator(new LaitDecorator(new Cafe())));
console.log(`${cafeComplet.description()} : ${cafeComplet.cout()}€`);
// "Café simple avec du lait avec du sucre avec de la cannelle : 3.5€"
Gestion avancée des objets
JavaScript offre plusieurs façons avancées de manipuler et contrôler les objets.
Object.defineProperty
Object.defineProperty
permet de définir des propriétés avec un contrôle précis sur leur comportement :
const personne = {};
Object.defineProperty(personne, 'nom', {
value: 'Alice',
writable: true, // peut être modifié
enumerable: true, // apparaît dans les boucles for...in
configurable: true // peut être supprimé
});
Object.defineProperty(personne, 'age', {
value: 30,
writable: false // ne peut pas être modifié
});
console.log(personne.nom); // "Alice"
personne.nom = "Bob";
console.log(personne.nom); // "Bob"
console.log(personne.age); // 30
personne.age = 40;
console.log(personne.age); // 30 (non modifié car writable: false)
Proxy
Les Proxys permettent de personnaliser le comportement des opérations fondamentales sur les objets :
const cible = {
message: "Hello",
prix: 100
};
const handler = {
// Interception de l'accès aux propriétés
get(cible, prop, receiver) {
console.log(`Accès à la propriété: ${prop}`);
if (prop === 'prix') {
return `${cible[prop]}€`;
}
return cible[prop];
},
// Interception de la modification de propriétés
set(cible, prop, valeur, receiver) {
console.log(`Modification de ${prop}: ${valeur}`);
if (prop === 'prix' && valeur < 0) {
throw new Error("Le prix ne peut pas être négatif");
}
cible[prop] = valeur;
return true; // succès
}
};
const proxy = new Proxy(cible, handler);
console.log(proxy.message); // Log: "Accès à la propriété: message" puis "Hello"
console.log(proxy.prix); // Log: "Accès à la propriété: prix" puis "100€"
proxy.message = "Bonjour"; // Log: "Modification de message: Bonjour"
// proxy.prix = -50; // Erreur: Le prix ne peut pas être négatif
Symboles et propriétés Well-Known
Les symboles peuvent être utilisés comme clés de propriétés uniques et non-énumérables :
// Création d'un symbole
const idSymbol = Symbol("id");
const utilisateur = {
nom: "Alice",
[idSymbol]: 12345 // propriété basée sur un symbole
};
console.log(utilisateur.nom); // "Alice"
console.log(utilisateur[idSymbol]); // 12345
// Les propriétés basées sur des symboles ne sont pas énumérables par défaut
for (let key in utilisateur) {
console.log(key); // Affiche seulement "nom", pas le symbole
}
// Symboles well-known pour personnaliser le comportement
const collectionPersonnalisee = {
elements: [1, 2, 3, 4, 5],
// Personnalisation de l'itération
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if (index < this.elements.length) {
return { value: this.elements[index++] * 2, done: false };
}
return { done: true };
}
};
}
};
// Utilisation avec for...of qui utilise l'itérateur
for (const item of collectionPersonnalisee) {
console.log(item); // 2, 4, 6, 8, 10
}
Exercices pratiques
Pour solidifier vos connaissances, essayez ces exercices :
-
Système de formes : Créez une hiérarchie de classes avec une classe
Forme
comme base, et des sous-classes commeCercle
,Rectangle
etTriangle
, chacune avec ses propres méthodes pour calculer l’aire et le périmètre. -
Gestion de bibliothèque : Implémentez un système de gestion de bibliothèque avec des classes pour
Livre
,Auteur
,Bibliothèque
etEmprunteur
. Utilisez l’encapsulation pour protéger les données et les mixins pour ajouter des fonctionnalités comme le suivi des emprunts. -
Mini framework UI : Créez un petit framework UI avec une classe
Component
comme base et des composants spécifiques commeButton
,Input
etPanel
qui héritent de cette base. Utilisez des méthodes commerender()
etupdate()
. -
Implémentation de patterns : Implémentez le pattern Singleton pour un service de journalisation et le pattern Factory pour créer différents types de notifications (email, SMS, push).
-
API de validation : Créez une API de validation avec des décorateurs pour valider les propriétés d’un objet (par exemple, longueur minimale d’une chaîne, plage de valeurs pour un nombre).
Exemple complet : Système de gestion de produits
Voici un exemple qui combine plusieurs concepts abordés dans ce tutoriel :
// Classe de base
class Produit {
#id;
#nom;
#prix;
#stock;
static #compteur = 0;
constructor(nom, prix, stock = 0) {
this.#id = ++Produit.#compteur;
this.#nom = nom;
this.#prix = prix;
this.#stock = stock;
}
// Getters/Setters
get id() { return this.#id; }
get nom() { return this.#nom; }
set nom(value) { this.#nom = value; }
get prix() { return this.#prix; }
set prix(value) {
if (value < 0) throw new Error("Le prix ne peut pas être négatif");
this.#prix = value;
}
get stock() { return this.#stock; }
set stock(value) {
if (value < 0) throw new Error("Le stock ne peut pas être négatif");
this.#stock = value;
}
// Méthodes
ajouter_stock(quantite) {
this.stock += quantite;
return this.stock;
}
retirer_stock(quantite) {
if (quantite > this.stock) throw new Error("Stock insuffisant");
this.stock -= quantite;
return this.stock;
}
toString() {
return `Produit #${this.id}: ${this.nom} - Prix: ${this.prix}€ - Stock: ${this.stock}`;
}
}
// Sous-classe spécialisée
class ProduitPerissable extends Produit {
#dateExpiration;
constructor(nom, prix, stock, dateExpiration) {
super(nom, prix, stock);
this.#dateExpiration = new Date(dateExpiration);
}
get dateExpiration() { return this.#dateExpiration; }
estPerime() {
return new Date() > this.#dateExpiration;
}
toString() {
return `${super.toString()} - Expire le: ${this.#dateExpiration.toLocaleDateString()}`;
}
}
// Mixin pour le suivi des modifications
const avecHistorique = Base => class extends Base {
#historique = [];
constructor(...args) {
super(...args);
}
ajouterEvenement(type, details) {
const evenement = {
type,
details,
date: new Date()
};
this.#historique.push(evenement);
return evenement;
}
get historique() {
return [...this.#historique];
}
};
// Application du mixin
class ProduitAvecHistorique extends avecHistorique(Produit) {
constructor(...args) {
super(...args);
this.ajouterEvenement("creation", { prix: args[1], stock: args[2] || 0 });
}
// Surcharge pour ajouter des événements
ajouter_stock(quantite) {
const stockAvant = this.stock;
const nouveauStock = super.ajouter_stock(quantite);
this.ajouterEvenement("ajout_stock", {
avant: stockAvant,
ajout: quantite,
apres: nouveauStock
});
return nouveauStock;
}
retirer_stock(quantite) {
const stockAvant = this.stock;
const nouveauStock = super.retirer_stock(quantite);
this.ajouterEvenement("retrait_stock", {
avant: stockAvant,
retrait: quantite,
apres: nouveauStock
});
return nouveauStock;
}
set prix(value) {
const ancienPrix = this.prix;
super.prix = value;
this.ajouterEvenement("changement_prix", {
avant: ancienPrix,
apres: value
});
}
}
// Factory pour créer différents types de produits
class ProduitFactory {
creerProduit(type, ...args) {
switch(type) {
case "standard":
return new ProduitAvecHistorique(...args);
case "perissable":
return new ProduitPerissable(...args);
default:
throw new Error(`Type de produit inconnu: ${type}`);
}
}
}
// Gestionnaire de catalogue (Singleton)
class CatalogueGestionnaire {
#produits = new Map();
static #instance;
constructor() {
if (CatalogueGestionnaire.#instance) {
return CatalogueGestionnaire.#instance;
}
CatalogueGestionnaire.#instance = this;
}
ajouterProduit(produit) {
this.#produits.set(produit.id, produit);
return produit;
}
getProduit(id) {
return this.#produits.get(id);
}
listerProduits() {
return [...this.#produits.values()];
}
listerProduitsEnStock() {
return this.listerProduits().filter(p => p.stock > 0);
}
}
// Démonstration
function demoProduits() {
// Création de produits avec Factory
const factory = new ProduitFactory();
const p1 = factory.creerProduit("standard", "Clavier", 49.99, 10);
const p2 = factory.creerProduit("standard", "Souris", 29.99, 15);
const p3 = factory.creerProduit("perissable", "Yaourt", 2.99, 50, "2023-12-31");
// Utilisation du singleton
const catalogue = new CatalogueGestionnaire();
catalogue.ajouterProduit(p1);
catalogue.ajouterProduit(p2);
catalogue.ajouterProduit(p3);
// Opérations
console.log("Produits en catalogue:");
catalogue.listerProduits().forEach(p => console.log(` - ${p}`));
p1.ajouter_stock(5);
p2.retirer_stock(3);
p1.prix = 45.99;
console.log("\nHistorique du clavier:");
p1.historique.forEach((e, i) => {
console.log(`${i+1}. [${e.date.toLocaleTimeString()}] ${e.type}: `, e.details);
});
console.log("\nTest d'un produit périssable:");
console.log(p3.toString());
console.log(`Est périmé: ${p3.estPerime()}`);
}
// Exécution
demoProduits();
Conclusion
Dans ce tutoriel, nous avons exploré la programmation orientée objet en JavaScript moderne :
- La syntaxe de classe moderne et ses fonctionnalités (constructeurs, getters/setters, méthodes statiques, champs privés)
- L’héritage et le polymorphisme via
extends
et la surcharge de méthodes - La composition et les mixins pour une réutilisation flexible du code
- Des patterns de conception courants comme Singleton, Factory, Observer et Décorateur
- Des techniques avancées de manipulation d’objets avec
Object.defineProperty
etProxy
JavaScript offre une approche flexible de la POO qui combine des éléments de programmation basée sur les prototypes avec une syntaxe plus familière inspirée des langages orientés objet classiques. Cette flexibilité vous permet de choisir le style qui convient le mieux à vos besoins, que ce soit l’héritage classique, la composition, ou une approche plus fonctionnelle.
Dans le prochain tutoriel, nous explorerons les Promises et async/await pour la gestion de l’asynchrone.
Besoin de revoir le destructuring ? Consultez notre tutoriel précédent sur le destructuring.
Prêt à continuer ? Passez au prochain tutoriel sur les Promises et async/await.
Commentaires
Les commentaires sont alimentés par GitHub Discussions
Connectez-vous avec GitHub pour participer à la discussion