تخطي إلى المحتوى الرئيسي

الفئات (Classes) في TypeScript

الفئات (Classes) في TypeScript

الدليل الشامل للفئات (Classes) في TypeScript: من الأساسيات إلى هندسة البرمجيات كائنية التوجه

المقدمة والنظرة الشاملة

تُعد الفئات (Classes) من الركائز الأساسية للبرمجة كائنية التوجه (Object-Oriented Programming – OOP)، وهي مفهوم محوري أحدث ثورة في كيفية تنظيم البرمجيات وبنائها. قبل ظهور الفئات في عالم JavaScript عبر معيار ECMAScript 2015 (ES6)، كان المطورون يعتمدون حصراً على النماذج الأولية (Prototypes) ودوال البناء لتمثيل الكائنات والوراثة، وهو أسلوب كان يتسم بالتعقيد وصعوبة الصيانة مع توسع حجم الأنظمة. مع ظهور TypeScript، تم تدعيم الفئات بنظام فحص صارم للأنواع ومعدّلات وصول دقيقة، مما جعلها حجر الأساس في تطوير التطبيقات الكبرى وتصميم أطر العمل العالمية.

المفاهيم الأساسية والأهمية

بعد أن تعرفنا في القسم السابق على الفئات (Classes) كنظرة عامة ومقدمة، حان الوقت لنتعمق في لب الموضوع ونستكشف المفاهيم الأساسية التي تشكل جوهر الفئات ودورها في إدارة البيانات وسلوك الكائنات. الفئة هي بمثابة “مخطط هندسي” (Blueprint) أو قالب مصمم لإنشاء كائنات متعددة (Instances) تشترك في نفس الخصائص والوظائف لكنها تحتفظ بحالات خاصة ومستقلة لكل كائن.

تكمن الأهمية الكبرى لاستخدام الفئات في قدرتها على تحقيق ركائز البرمجة كائنية التوجه الأساسية:

  • التغليف (Encapsulation): عزل البيانات الحساسة داخل الفئة وتوفير واجهات وصول محكومة تمنع التعديل غير المباشر.
  • الوراثة (Inheritance): تمكين الفئات الفرعية من وراثة سلوك وخصائص الفئات الأم وإعادة استخدام الشيفرة دون تكرار.
  • تعدد الأشكال (Polymorphism): قدرة الفئات المشتقة على تعديل سلوك الدوال الموروثة عبر إعادة التعريف (Method Overriding) بما يلائم سياقها الخاص.
  • التجريد (Abstraction): إخفاء تفاصيل العمليات المعقدة والداخلية وكشف الوظائف الأساسية فقط للمستهلك البرمجي.

الشرح التفصيلي والخطوات العملية

بعد أن استعرضنا المبادئ الأساسية وأهمية الفئات (Classes) في البرمجة كائنية التوجه، حان الوقت لنتعمق في كيفية بناء هذه الفئات واستخدامها عملياً في TypeScript. سنكتشف التركيب النحوي، والخصائص، والدوال، وكيفية التفاعل معها لتشكيل كائنات قوية ومرنة.

بناء الفئات (Classes) في TypeScript

تبدأ كل فئة بتعريف بسيط باستخدام الكلمة المفتاحية class، متبوعة باسم الفئة (الذي يُفضل أن يبدأ بحرف كبير وفقاً للاتفاقيات الدولية). داخل الفئة، نقوم بتحديد الخصائص (Properties) التي تمثل البيانات، والدوال (Methods) التي تمثل السلوك.

class سيارة {
    // الخصائص (Properties)
    ماركة: string;
    موديل: number;
    اللون: string;
    السرعة: number = 0; // قيمة افتراضية

    // الدالة البانية (Constructor)
    constructor(ماركة: string, موديل: number, اللون: string) {
        this.ماركة = ماركة;
        this.موديل = موديل;
        this.اللون = اللون;
    }

    // الدوال (Methods)
    تسريع(قيمة: number): void {
        this.السرعة += قيمة;
        console.log(`السيارة ${this.ماركة} تسرع إلى ${this.السرعة} كم/س.`);
    }

    فرملة(قيمة: number): void {
        this.السرعة -= قيمة;
        if (this.السرعة < 0) {
            this.السرعة = 0;
        }
        console.log(`السيارة ${this.ماركة} تبطئ إلى ${this.السرعة} كم/س.`);
    }
}

في هذا المثال، لدينا فئة سيارة تحتوي على خصائص مثل ماركة و موديل و اللون، ودوال مثل تسريع و فرملة. لاحظ استخدام الكلمة المفتاحية this للإشارة إلى خصائص ودوال الكائن الحالي الذي يتم التفاعل معه أثناء التشغيل.

الدالة البانية (Constructor)

الدالة البانية هي دالة خاصة يتم استدعاؤها تلقائياً عند إنشاء كائن جديد من الفئة عبر المعامل new. وظيفتها الأساسية هي تهيئة خصائص الكائن بقيم ابتدائية وتطبيق المنطق التأسيسي. في TypeScript، توفر اللغة ميزة متقدمة تسمى خصائص المعاملات (Parameter Properties) تتيح دمج تعريف الحقول وإسنادها في سطر واحد داخل دالة البناء:

class ModernCar {
    // تعريف الخصائص وإسنادها تلقائياً عبر دالة البناء
    constructor(
        public brand: string,
        public model: number,
        public color: string,
        private _speed: number = 0
    ) {}

    public get speed(): number {
        return this._speed;
    }
}

أفضل الممارسات والنصائح التطبيقية

بعد أن تعمقنا في فهم الفئات (Classes) في TypeScript واستعرضنا آليات عملها، حان الوقت للانتقال إلى الجانب العملي والتطبيقي، حيث سنستكشف أفضل الممارسات البرمجية المعتمدة في تصميم وبناء الفئات:

  • تطبيق مبدأ المسؤولية الفردية (Single Responsibility Principle): يجب أن تركز كل فئة على إدارة مفهوم واحد فقط داخل النظام، وتجنب الفئات المتضخمة (God Classes).
  • استخدام معدّلات الوصول بصرامة: اجعل الخصائص الداخلية private أو protected ولا تعرض سوى الواجهات الضرورية كـ public.
  • الاعتماد على الحقول الثابتة غير القابلة للتعديل (readonly): عند وجود حقول تُسند لمرة واحدة فقط أثناء الإنشاء (مثل المعرفات ومفاتيح الربط)، استخدم readonly لحمايتها من التعديل العرضي.
  • تفضيل التركيب على الوراثة (Composition over Inheritance): تجنب بناء سلاسل وراثة عميقة ومعقدة تفوق مستويين أو ثلاثة؛ واستبدل ذلك بدمج كائنات متخصصة تؤدي المهام الفرعية.
  • استخدام دوال الوصول المخصصة (Getters & Setters): للتحقق من صحة المدخلات قبل تعديل القيم الحساسة في الذاكرة.

مقارنات وتحليلات متقدمة

بعد أن استعرضنا الفئات (Classes) في TypeScript من جوانبها الأساسية والتطبيقية وأفضل الممارسات، ننتقل الآن إلى مستوى أكثر عمقاً، حيث نخوض في مقارنات تقنية تفصيلية بين الفئات، والواجهات (Interfaces)، والأسماء المستعارة للأنواع (Type Aliases):

وجه المقارنةالفئات (Classes)الواجهات (Interfaces)الأسماء المستعارة (Type Aliases)
التواجد في وقت التشغيلنعم (تتحول إلى دوال/فئات JS حقيقية)لا (تُحذف بالكامل عند الترجمة)لا (تُحذف بالكامل عند الترجمة)
تضمين التنفيذ الفعلينعم (تحتوي دوال وحقول وقيم ابتدائية)لا (تعريف توقيعات وعقود فقط)لا (تعريف أنواع فقط)
إنشاء الكائنات بالمعامل newنعم (تنتج كائنات حية)لا يمكن استدعاؤها بـ newلا يمكن استدعاؤها بـ new
دعم أنواع الاتحاد والتقاطعغير مدعوم بشكل مباشر في الاسمغير مدعوم لأنواع الاتحاد البسيطةمدعوم بمرونة تامة (A | B أو A & B)

الفئات المجردة (Abstract Classes) مقابل الواجهات (Interfaces)

الواجهة تمثل عقداً صرفاً بدون أي سطر برمجي تنفيذي، بينما تمكنك الفئة المجردة (Abstract Class) من الجمع بين مشاركة كود فعلي مشترك (Concrete Methods) وإلزام الفئات المشتقة بتنفيذ دوال مجردة معينة (Abstract Methods):

abstract class DatabaseConnector {
    // طريقة ملموسة مشتركة
    public logConnection(dbName: string): void {
        console.log(`[DB LOG] جاري الاتصال بقاعدة البيانات: ${dbName}`);
    }

    // طريقة مجردة ملزمة للفئات الفرعية
    abstract connect(): Promise<boolean>;
}

class PostgresConnector extends DatabaseConnector {
    async connect(): Promise<boolean> {
        this.logConnection("PostgreSQL");
        // كود الاتصال الفعلي
        return true;
    }
}

حل المشكلات والأخطاء الشائعة في الفئات (Classes)

بعد أن استعرضنا الفئات (Classes) في TypeScript من جوانبها النظرية والتطبيقية، وتعمقنا في أفضل الممارسات والمقارنات المتقدمة، نناقش الآن أبرز المشكلات البرمجية التي تواجه المطورين عند بناء الفئات وكيفية معالجتها هندسياً:

1. فقدان سياق الكائن (Loss of ‘this’ Binding)

عند تمرير دوال الفئة كوسيطات نداء خلفي (Callbacks) أو داخل مستمعي الأحداث، يفقد المتغير this ارتباطه بالكائن الأصلي ويصبح undefined. لحل هذه المشكلة، استخدم الدوال السهمية (Arrow Functions) كخصائص للفئة لربط السياق تلقائياً:

class EventHandler {
    private message: string = "حدث تشغيل الكود بنجاح";

    // ✅ الدالة السهمية تحتفظ بربط this تلقائياً في الذاكرة
    public handleClick = (): void => {
        console.log(this.message);
    };
}

const handler = new EventHandler();
const callback = handler.handleClick;
callback(); // يطبع الرسالة بدون أخطاء Runtime

2. خطأ الخصائص غير المهيأة (Strict Property Initialization)

عند تفعيل خيار strictPropertyInitialization في ملف tsconfig.json، سيرمي المترجم خطأً إذا تم الإعلان عن خاصية دون إعطائها قيمة ابتدائية أو تهيئتها داخل الـ Constructor. الحل هو إما تهيئتها في دالة البناء، أو جعل نوعها يقبل undefined، أو استخدام عامل التأكيد الحاسم ! في حال كانت الخاصية تُهيأ عبر إطار عمل خارجي.

دراسات حالة وأمثلة واقعية

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

دراسة حالة 1: نظام إدارة المنتجات في متجر إلكتروني متكامل

نظام عملي لنمذجة المنتجات داخل المتجر مع آليات التحقق من صحة الأسعار، وتطبيق التخفيضات، وتحديث الحالات:

class Product {
    private id: string;
    public name: string;
    public price: number;
    public description: string;
    private createdAt: Date;

    constructor(id: string, name: string, price: number, description: string) {
        this.id = id;
        this.name = name;
        this.price = price;
        this.description = description;
        this.createdAt = new Date(); // تعيين تاريخ الإنشاء تلقائياً
    }

    public displayProductDetails(): void {
        console.log(`--- تفاصيل المنتج ---`);
        console.log(`المعرف: ${this.id}`);
        console.log(`الاسم: ${this.name}`);
        console.log(`السعر: $${this.price.toFixed(2)}`);
        console.log(`الوصف: ${this.description}`);
        console.log(`تاريخ الإنشاء: ${this.createdAt.toLocaleDateString()}`);
    }

    public updatePrice(newPrice: number): boolean {
        if (newPrice <= 0) {
            console.error("خطأ: يجب أن يكون السعر الجديد قيمة موجبة أكبر من الصفر.");
            return false;
        }
        this.price = newPrice;
        console.log(`تم تحديث سعر المنتج "${this.name}" بنجاح إلى $${this.price.toFixed(2)}`);
        return true;
    }

    public applyDiscount(percentage: number): void {
        if (percentage < 0 || percentage > 100) {
            console.error("خطأ: نسبة الخصم يجب أن تكون بين 0 و 100.");
            return;
        }
        const discountAmount = (this.price * percentage) / 100;
        this.price -= discountAmount;
        console.log(`تم تطبيق خصم ${percentage}%! السعر الجديد: $${this.price.toFixed(2)}`);
    }
}

// تجربة استخدام الفئة عملياً
const laptop = new Product("PROD-101", "حاسوب محمول مخصص للبرمجة", 1200, "أداء عالي ومعالج فائق السرعة");
laptop.displayProductDetails();
laptop.applyDiscount(15);
laptop.updatePrice(1050);

دراسة حالة 2: نظام سلة المشتريات والمدفوعات (Shopping Cart Engine)

interface CartItem {
    product: Product;
    quantity: number;
}

class ShoppingCart {
    private items: CartItem[] = [];

    public addItem(product: Product, quantity: number = 1): void {
        const existingIndex = this.items.findIndex(item => item.product.name === product.name);
        if (existingIndex > -1) {
            this.items[existingIndex].quantity += quantity;
        } else {
            this.items.push({ product, quantity });
        }
        console.log(`تمت إضافة ${quantity} من "${product.name}" إلى السلة.`);
    }

    public calculateTotal(): number {
        return this.items.reduce((total, item) => total + (item.product.price * item.quantity), 0);
    }

    public displayCartSummary(): void {
        console.log(`=== ملخص سلة المشتريات ===`);
        this.items.forEach(item => {
            console.log(`- ${item.product.name} × ${item.quantity} = $${(item.product.price * item.quantity).toFixed(2)}`);
        });
        console.log(`الإجمالي النهائي: $${this.calculateTotal().toFixed(2)}`);
    }
}

const cart = new ShoppingCart();
cart.addItem(laptop, 2);
cart.displayCartSummary();

الأسئلة الشائعة وإجاباتها حول الفئات في TypeScript

ما هي الفئات (Classes) في TypeScript؟

الفئات (Classes) في TypeScript تمثل البنية الأساسية لبناء التطبيقات المعقدة والمتطورة. هي عبارة عن تعريفات للبيانات والسلوكيات المتعلقة بها. تساعد الفئات في تنظيم الأكواد البرمجية وتحسين جودة البرمجيات عن طريق تقسيم المهام المعقدة إلى مكونات أصغر وأكثر قابلية للإدارة.

ما هي أهمية استخدام الفئات (Classes) في TypeScript؟

استخدام الفئات (Classes) في TypeScript يعزز قابلية قراءة وصيانة الأكواد البرمجية. توفر الفئات طريقة منظمة لتعريف البيانات والسلوكيات المتعلقة بها، مما يساعد في تحسين جودة البرمجيات وتقليل الأخطاء البرمجية. كما تعتبر الفئات الأساس لبناء التطبيقات المعقدة والمركبة، حيث تمكن المطورين من تقسيم المهام الكبيرة إلى أجزاء أصغر وأكثر تحكمًا.

كيف يمكنني إنشاء فئة جديدة في TypeScript؟

لإنشاء فئة جديدة في TypeScript، يمكنك اتباع الخطوات التالية:

  • ابدأ بتعريف اسم الفئة باستخدام الكلمة المفتاحية “class”.
  • حدد الخصائص (Properties) والطرق (Methods) التي تحتاجها الفئة.
  • استخدم الكلمات المفتاحية المناسبة لتعريف الخصائص والطرق (مثل public, private, protected).
  • نفذ الكود النموذجي للفئة واختبر وظائفها عبر إنشاء كائن باستخدام المعامل new.

على سبيل المثال، لنعمل على إنشاء فئة بسيطة تحسب مجموع عددين صحيحين:

class Calculator {
    constructor(private num1: number, private num2: number) {}
    
    getResult(): number {
        return this.num1 + this.num2;
    }
}

const calc = new Calculator(15, 25);
console.log(calc.getResult()); // الناتج: 40

في هذه الفئة البسيطة، قمنا بتعريف مُنشئ (Constructor) يأخذ معاملين خاصين من نوع number، ثم قمنا بتعريف طريقة getResult() التي تقوم بجمع القيمتين وتعيد النتيجة بالأمان النوعي الكامل.

ما هي الخصائص (Properties) والطرق (Methods) في TypeScript؟

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

كيف يمكنني استخدام الخصائص والطرق في شفرتي؟

  • للوصول إلى الخصائص الخاصة والتحكم فيها، استخدم دوال القراءة والتعديل get و set.
  • لإنشاء كائن حي من فئة معينة، استخدم المعامل new متبوعاً بدالة البناء.
  • لاستدعاء الطرق، استخدم عامل النقطة المباشر instance.methodName().

هل هناك أي مفاهيم متقدمة للفئات في TypeScript يجب أن أتعلمها؟

توفر TypeScript ميزات متقدمة للفئات تشمل: الوراثة عبر extends، تنفيذ الواجهات عبر implements، الفئات المجردة abstract classes، الخصائص الثابتة static، وحقول الهاش الخاصة الحقيقية #private.

ما الفرق بين الفئات (Classes) والواجهات (Interfaces) في TypeScript؟

الفئات (Classes) توفر الخصائص والطرق مع تنفيذها العملي، وتظل متواجدة بعد الترجمة إلى JavaScript لتوليد كائنات حية. بينما الواجهات (Interfaces) هي عقود وصفية بحتة تُحذف كلياً أثناء عملية الترجمة ولا تترك أي بصمة في حجم الملف المترجم.

الخلاصة والخطوات القادمة

وصلنا في رحلتنا الشيقة والمفصلة إلى محطة الخلاصة فيما يخص الفئات (Classes) في TypeScript. لقد استعرضنا معاً هذه البنية البرمجية المحورية من زوايا متعددة، بدءاً من أساسياتها، مروراً بأهميتها في البرمجة كائنية التوجه (OOP)، وصولاً إلى آليات عملها المعقدة، وأفضل الممارسات لتطبيقها، وتحليل مقارن لمزاياها، وكيفية التعامل مع التحديات الشائعة، وانتهاءً بدراسات حالة واقعية وأسئلة متكررة. إن إتقان الفئات ليس مجرد خطوة في مسيرتك التعليمية لـ TypeScript، بل هو قفزة نوعية نحو بناء تطبيقات أكثر قوة، قابلية للصيانة، وذات كود منظم.

ملخص رحلتنا مع الفئات (Classes)

خلال هذا القسم التدريبي، رسخنا فهمنا بأن الفئات في TypeScript هي أكثر من مجرد “سكر نحوي” (Syntactic Sugar) فوق دوال البناء في JavaScript. إنها تمثل حجر الزاوية الذي يمكننا من تجسيد مبادئ البرمجة كائنية التوجه الأساسية: التغليف (Encapsulation)، الوراثة (Inheritance)، وتعدد الأشكال (Polymorphism)، والتجريد (Abstraction) بطريقة منظمة وواضحة ومعيارية.

لقد رأينا كيف أن TypeScript يعزز هذه الفئات بميزات قوية مثل تحديد الأنواع الثابتة الصارمة، ومعدّلات الوصول الدقيقة، والخصائص المدمجة في دوال البناء. في الدروس القادمة، سنبني فوق هذه المعرفة لاستكشاف الأنماط المعمارية المتقدمة (Design Patterns) وتطبيق حقن التبعيات (Dependency Injection) لبناء تطبيقات متقدمة وسريعة تلبي أعلى معايير الجودة البرمجية.


اكتشاف المزيد من كود التطور

اشترك للحصول على أحدث التدوينات المرسلة إلى بريدك الإلكتروني.

🎓 التقدم التعليمي في هذا الدرس
هل أتممت فهم وتطبيق هذا الدرس؟ اضغط على الزر لتسجيل إنجازه وكسب 10 نقاط!
MOHAMMAD ABU KASHREEF

MOHAMMAD ABU KASHREEF

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

اترك رد