0%
JavaScript الحديث: البرمجة كائنية التوجّه

البرمجة كائنية التوجّه

الأصناف والكائنات الحديثة

10-15 دقيقة

JavaScript الحديث: البرمجة كائنية التوجّه

JavaScript لغة مرنة تدعم عدة نماذج برمجية، من بينها البرمجة كائنية التوجّه (POO). منذ ES6 أضافت JavaScript صيغة أكثر بداهة للأصناف (classes)، مع أن النموذج الكامن وراءها ما زال قائمًا على النماذج الأولية (prototypes). سنستكشف في هذا الدرس البرمجة كائنية التوجّه في JavaScript الحديث، بما في ذلك الأصناف والوراثة والـ mixins وأنماط التصميم الشائعة الاستخدام.

النموذج الكائني في JavaScript

قبل استكشاف الصيغة الحديثة للأصناف، من المهم فهم كيفية عمل النموذج الكائني في JavaScript.

الكائنات والنماذج الأولية

في JavaScript، كل شيء تقريبًا كائن، وتعمل الوراثة عبر النماذج الأولية (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"

دوال البناء (الطريقة القديمة)

قبل ES6، كان إنشاء كائنات متشابهة يتم عادةً باستخدام دوال البناء (constructor functions):

// 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"

الأصناف في JavaScript الحديث (ES6+)

أضافت ES6 صيغة أكثر بداهة للأصناف تبسّط إنشاء الكائنات وتنفيذ الوراثة.

تعريف صنف

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"

الخصائص والدوال الساكنة (static)

تنتمي الدوال والخصائص الساكنة إلى الصنف نفسه، لا إلى النسخ (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

المُحصِّلات والمُعيِّنات (getters و setters)

تتيح المُحصِّلات والمُعيِّنات التحكم في الوصول إلى خصائص الصنف:

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

حقول الصنف (Class Fields)

تتيح حقول الصنف، التي أُضيفت في الإصدارات الأحدث من JavaScript، التصريح بالخصائص مباشرةً داخل الصنف:

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

الوراثة وتعدد الأشكال

تتيح الوراثة لصنف ما أن يرث خصائص ودوال صنف آخر.

الوراثة باستخدام 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"

سلسلة النماذج الأولية

حتى مع الصيغة الحديثة للأصناف، لا تزال JavaScript تستخدم النماذج الأولية في الخلفية:

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

التحقق من النوع

للتحقق مما إذا كان كائن ما نسخة من صنف معيّن، استخدم العامل 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) والـ Mixins

في بعض الأحيان لا تكفي الوراثة وحدها لنمذجة العلاقات المعقدة. يوفّر التركيب والـ mixins بدائل مرنة.

التركيب

يقوم التركيب على دمج كائنات أبسط لإنشاء كائنات أكثر تعقيدًا:

// 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 الوظيفية

الـ mixins الوظيفية هي دوال تضيف خصائص ودوال إلى كائن موجود:

// 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"

أنماط التصميم في JavaScript

أنماط التصميم هي حلول قابلة لإعادة الاستخدام لمشكلات متكررة. إليك بعض الأنماط الشائعة في JavaScript.

Singleton

يضمن نمط Singleton أن يكون للصنف نسخة واحدة فقط، ويوفّر نقطة وصول عامة إلى هذه النسخة:

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 (المصنع)

ينشئ نمط Factory الكائنات دون كشف منطق الإنشاء:

// 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 (المراقِب)

يتيح نمط Observer لكائن ما إشعار كائنات أخرى بتغيّر حالته:

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 (الوحدة)

يغلّف نمط Module الوظائف ويكشف واجهة برمجية عامة (API) مع إبقاء بعض التفاصيل خاصة:

// وحدة باستخدام IIFE (الأسلوب القديم)
const Compteur = (function() {
    // متغيرات خاصة
    let compte = 0;
    
    // دوال خاصة
    function valider(valeur) {
        return typeof valeur === 'number' && !isNaN(valeur);
    }
    
    // واجهة برمجية عامة
    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 (خاص)

Decorator (المُزخرِف)

يتيح نمط Decorator إضافة سلوكيات إلى كائنات بعينها دون التأثير في سلوك الكائنات الأخرى من الفئة نفسها:

// المكوّن الأساسي
class Cafe {
    cout() {
        return 2.5;
    }
    
    description() {
        return "Café simple";
    }
}

// المُزخرِفات
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`;
    }
}

// الاستخدام
let monCafe = new Cafe();
console.log(`${monCafe.description()} : ${monCafe.cout()}€`);
// "Café simple : 2.5€"

// إضافة الحليب
monCafe = new LaitDecorator(monCafe);
console.log(`${monCafe.description()} : ${monCafe.cout()}€`);
// "Café simple avec du lait : 3€"

// إضافة السكر
monCafe = new SucreDecorator(monCafe);
console.log(`${monCafe.description()} : ${monCafe.cout()}€`);
// "Café simple avec du lait avec du sucre : 3.2€"

// إنشاء قهوة مخصصة مباشرة
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€"

الإدارة المتقدمة للكائنات

توفّر JavaScript عدة طرق متقدمة للتعامل مع الكائنات والتحكم فيها.

Object.defineProperty

تتيح Object.defineProperty تعريف الخصائص مع تحكم دقيق في سلوكها:

const personne = {};

Object.defineProperty(personne, 'nom', {
    value: 'Alice',
    writable: true,      // يمكن تعديلها
    enumerable: true,    // تظهر في حلقات for...in
    configurable: true   // يمكن حذفها
});

Object.defineProperty(personne, 'age', {
    value: 30,
    writable: false      // لا يمكن تعديلها
});

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 (لم تتغير لأن writable: false)

Proxy

تتيح كائنات Proxy تخصيص سلوك العمليات الأساسية على الكائنات:

const cible = {
    message: "Hello",
    prix: 100
};

const handler = {
    // اعتراض الوصول إلى الخصائص
    get(cible, prop, receiver) {
        console.log(`Accès à la propriété: ${prop}`);
        
        if (prop === 'prix') {
            return `${cible[prop]}€`;
        }
        
        return cible[prop];
    },
    
    // اعتراض تعديل الخصائص
    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; // نجاح
    }
};

const proxy = new Proxy(cible, handler);

console.log(proxy.message); // Log: "Accès à la propriété: message" ثم "Hello"
console.log(proxy.prix); // Log: "Accès à la propriété: prix" ثم "100€"

proxy.message = "Bonjour"; // Log: "Modification de message: Bonjour"
// proxy.prix = -50; // خطأ: Le prix ne peut pas être négatif

الرموز (Symbols) والخصائص المعروفة (Well-Known)

يمكن استخدام الرموز كمفاتيح خصائص فريدة وغير قابلة للعدّ:

// إنشاء رمز
const idSymbol = Symbol("id");

const utilisateur = {
    nom: "Alice",
    [idSymbol]: 12345  // خاصية مبنية على رمز
};

console.log(utilisateur.nom); // "Alice"
console.log(utilisateur[idSymbol]); // 12345

// الخصائص المبنية على الرموز غير قابلة للعدّ افتراضيًا
for (let key in utilisateur) {
    console.log(key); // يعرض "nom" فقط، وليس الرمز
}

// رموز well-known لتخصيص السلوك
const collectionPersonnalisee = {
    elements: [1, 2, 3, 4, 5],
    
    // تخصيص عملية التكرار
    [Symbol.iterator]() {
        let index = 0;
        return {
            next: () => {
                if (index < this.elements.length) {
                    return { value: this.elements[index++] * 2, done: false };
                }
                return { done: true };
            }
        };
    }
};

// الاستخدام مع for...of التي تستعمل المكرِّر
for (const item of collectionPersonnalisee) {
    console.log(item); // 2, 4, 6, 8, 10
}

تمارين عملية

لترسيخ معارفك، جرّب هذه التمارين:

  1. نظام الأشكال: أنشئ تسلسلًا هرميًا من الفئات يضم فئة Forme كأساس، وفئات فرعية مثل Cercle وRectangle وTriangle، تتضمن كل منها دوالها الخاصة لحساب المساحة والمحيط.

  2. إدارة مكتبة: نفّذ نظامًا لإدارة مكتبة يضم الفئات Livre وAuteur وBibliothèque وEmprunteur. استخدم التغليف (encapsulation) لحماية البيانات، واستخدم mixins لإضافة وظائف مثل تتبّع الإعارات.

  3. إطار عمل UI مصغّر: أنشئ إطار عمل صغيرًا لواجهات المستخدم يضم فئة Component كأساس ومكوّنات محددة مثل Button وInput وPanel ترث من هذا الأساس. استخدم دوال مثل render() وupdate().

  4. تطبيق الأنماط: نفّذ نمط Singleton لخدمة تسجيل السجلات (journalisation)، ونمط Factory لإنشاء أنواع مختلفة من الإشعارات (email وSMS وpush).

  5. واجهة برمجية للتحقق: أنشئ واجهة برمجية للتحقق باستخدام decorators للتحقق من خصائص كائن (مثل الحد الأدنى لطول سلسلة نصية، أو نطاق القيم المسموح به لرقم).

مثال كامل: نظام إدارة المنتجات

إليك مثالًا يجمع عدة مفاهيم تناولناها في هذا الدرس:

// الفئة الأساسية
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;
    }
    
    // الدوال
    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}`;
    }
}

// فئة فرعية متخصصة
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 لتتبّع التعديلات
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];
    }
};

// تطبيق الـ mixin
class ProduitAvecHistorique extends avecHistorique(Produit) {
    constructor(...args) {
        super(...args);
        this.ajouterEvenement("creation", { prix: args[1], stock: args[2] || 0 });
    }
    
    // إعادة تعريف الدوال لإضافة الأحداث
    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 لإنشاء أنواع مختلفة من المنتجات
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}`);
        }
    }
}

// مدير الكتالوج (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);
    }
}

// عرض توضيحي
function demoProduits() {
    // إنشاء المنتجات باستخدام 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");
    
    // استخدام الـ singleton
    const catalogue = new CatalogueGestionnaire();
    catalogue.ajouterProduit(p1);
    catalogue.ajouterProduit(p2);
    catalogue.ajouterProduit(p3);
    
    // العمليات
    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()}`);
}

// التنفيذ
demoProduits();

الخلاصة

في هذا الدرس استكشفنا البرمجة كائنية التوجه في JavaScript الحديثة:

  • صياغة الفئات الحديثة وميزاتها (المنشئات، وgetters/setters، والدوال الساكنة، والحقول الخاصة)
  • الوراثة وتعدد الأشكال (polymorphism) عبر extends وإعادة تعريف الدوال
  • التركيب (composition) وmixins لإعادة استخدام الشيفرة بمرونة
  • أنماط التصميم الشائعة مثل Singleton وFactory وObserver وDecorator
  • تقنيات متقدمة للتعامل مع الكائنات باستخدام Object.defineProperty وProxy

توفّر JavaScript مقاربة مرنة للبرمجة كائنية التوجه، تجمع بين عناصر البرمجة القائمة على النماذج الأولية (prototypes) وصياغة أكثر ألفة مستوحاة من اللغات كائنية التوجه التقليدية. وتتيح لك هذه المرونة اختيار الأسلوب الأنسب لاحتياجاتك، سواء كان الوراثة التقليدية أو التركيب أو مقاربة أقرب إلى البرمجة الوظيفية.

في الدرس القادم، سنتناول Promises وasync/await لإدارة العمليات غير المتزامنة.


هل تحتاج إلى مراجعة التفكيك (destructuring)؟ راجع الدرس السابق عن destructuring.

هل أنت مستعد للمتابعة؟ انتقل إلى الدرس التالي عن Promises وasync/await.

التعليقات

التعليقات مدعومة عبر GitHub Discussions

سجّل الدخول باستخدام GitHub للمشاركة في النقاش

تم نسخ الرابط!