التغليف في لغة JavaScript


التغليف في لغة JavaScript

يا هلا والله! اليوم بنتكلم عن مفهوم مهم في البرمجة: التغليف (Encapsulation) في جافاسكريبت. الموضوع بسيط وحلو وبيخلي كودك أنظف وأكثر أمانًا.

إيش هو التغليف باختصار؟

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

ليش نحتاج التغليف؟

  • أمان البيانات: يمنع التعديل العشوائي على البيانات من الخارج.
  • سهولة الصيانة: لو احتجت تغير طريقة عمل شيء داخلي، ما يحتاج تغير كل مكان يستخدم هذا الشيء.
  • تنظيم الكود: يخلي الكود مرتب ومفهوم أكثر، كل جزء مسؤول عن وظيفته الخاصة.
  • المرونة: تقدر تغير التنفيذ الداخلي بدون ما تأثر على الأجزاء الثانية من الكود.

كيف نسوي تغليف في جافاسكريبت؟

جافاسكريبت ما عندها كلمات مفتاحية صريحة زي private و public زي بعض اللغات الثانية، لكن نقدر نحقق التغليف بطرق ذكية. أشهرها:

1. عن طريق الـ Closures (الإغلاقات)

هذي الطريقة هي الأساس في جافاسكريبت لتحقيق التغليف قبل ظهور الكلاسات والموديلز. فكرتها إن الدالة الداخلية تقدر توصل للمتغيرات حقت الدالة الخارجية حتى بعد ما تكون الدالة الخارجية انتهت من تنفيذها.

ملاحظة سريعة: الـ closure هي دالة تتذكر البيئة اللي انشأت فيها.

شوف هالمثال:

function createCounter() {
    let count = 0; // هذا متغير خاص (private)

    return {
        increment: function() {
            count++;
            console.log('Count:', count);
        },
        decrement: function() {
            count--;
            console.log('Count:', count);
        },
        getCount: function() {
            return count;
        }
    };
}

const myCounter = createCounter();
myCounter.increment(); // Count: 1
myCounter.increment(); // Count: 2
console.log(myCounter.getCount()); // 2

// ما تقدر توصل لـ <code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">count</code> مباشرة
// console.log(myCounter.count); // undefined

في المثال اللي فوق، المتغير count خاص بالدالة createCounter وما تقدر توصل له مباشرة من برا. بس تقدر تتفاعل معاه عن طريق الدوال increment و decrement و getCount اللي رجعناها.

2. عن طريق الـ Classes (ES6)

مع ES6، جاتنا الكلاسات اللي سهلت علينا كتابة الكود اللي يعتمد على الكائنات. تقدر تسوي حقول (fields) ودوال (methods) خاصة باستخدام علامة الـ #.

تنبيه: الحقول الخاصة (private class fields) جديدة نسبياً وممكن ما تكون مدعومة في كل المتصفحات القديمة أو بيئات التشغيل. تأكد من دعمها قبل استخدامها في المشاريع الكبيرة.

مثال على كلاس يستخدم الحقول الخاصة:

class BankAccount {
    #balance = 0; // هذا حقل خاص (private field)

    constructor(initialBalance) {
        if (initialBalance >= 0) {
            this.#balance = initialBalance;
        } else {
            console.error("Initial balance cannot be negative.");
        }
    }

    deposit(amount) {
        if (amount > 0) {
            this.#balance += amount;
            console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">Deposited ${amount}. New balance: ${this.#balance}</code>);
        } else {
            console.error("Deposit amount must be positive.");
        }
    }

    withdraw(amount) {
        if (amount > 0 && amount <= this.#balance) {
            this.#balance -= amount;
            console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">Withdrew ${amount}. New balance: ${this.#balance}</code>);
        } else {
            console.error("Invalid withdrawal amount or insufficient funds.");
        }
    }

    getBalance() {
        return this.#balance;
    }
}

const myAccount = new BankAccount(100);
myAccount.deposit(50); // Deposited 50. New balance: 150
myAccount.withdraw(30); // Withdrew 30. New balance: 120
console.log(myAccount.getBalance()); // 120

// محاولة الوصول للحقل الخاص مباشرة بتفشل
// console.log(myAccount.#balance); // SyntaxError: Private field '#balance' must be declared in an enclosing class

هنا، #balance حقل خاص وما تقدر توصل له أو تعدله إلا عن طريق الدوال اللي داخل الكلاس نفسه، زي deposit و withdraw.

3. عن طريق الـ Modules (الوحدات)

الموديلز في جافاسكريبت (ES modules) توفر تغليف بشكل طبيعي. أي شيء ما تسوي له export من الموديل يعتبر خاص فيه وما تقدر توصل له من برا الموديل.

لو عندك ملف اسمه calculator.js:

// calculator.js
let total = 0; // هذا متغير خاص بالموديل

function add(num) {
    total += num;
    return total;
}

function subtract(num) {
    total -= num;
    return total;
}

export { add, subtract }; // فقط الدوال هذي هي اللي تقدر توصل لها من برا

وفي ملف ثاني اسمه app.js:

// app.js
import { add, subtract } from './calculator.js';

console.log(add(10));    // 10
console.log(add(5));     // 15
console.log(subtract(7)); // 8

// ما تقدر توصل لـ <code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">total</code> مباشرة
// console.log(total); // ReferenceError: total is not defined (لو هذا السطر كان في app.js)

هنا، المتغير total خاص بملف calculator.js وما تقدر تشوفه أو تعدله من app.js، إلا عن طريق الدوال اللي سوينا لها export.

الخلاصة

التغليف مفهوم أساسي في البرمجة الكائنية، وجافاسكريبت توفر لك طرق مختلفة عشان تطبقه، سواء باستخدام الـ closures، أو الـ classes مع الحقول الخاصة، أو حتى عن طريق استخدام الـ modules اللي تعطيك تغليف طبيعي. استخدامك للتغليف بيخلي كودك أقوى، أنظف، وأسهل في الصيانة والتطوير.

أتمنى الدرس كان خفيف ومفيد لكم! نشوفكم في دروس ثانية.