الوحدة 11: الكائنات والكلاسات (Objects and Classes)

اللبنة الأساسية للبرمجة الكائنية في جافا

المدة: أسبوعان 6 ساعات نظرية 6 ساعات عملية

المقدمة والأهداف التعليمية

تهانينا على وصولك إلى هذه المرحلة المتقدمة! بعد أن أصبحتَ بارعاً في تنظيم الكود باستخدام الدوال والتعامل مع مجموعات البيانات باستخدام المصفوفات، حان الوقت للانتقال إلى المفهوم الذي تُبنى عليه قوة جافا الحقيقية: **البرمجة الكائنية التوجه (Object-Oriented Programming - OOP)**. هذه الوحدة هي نقطة تحول جوهرية في رحلتك البرمجية.

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

الأهداف:

بنهاية هذه الوحدة، ستكون قادرًا على:

  • فهم مفهوم **الكلاس (Class)** كـ "قالب" أو "مخطط" لإنشاء الكائنات.
  • فهم مفهوم **الكائن (Object)** كـ "نسخة حية" من الكلاس.
  • التمييز بين **الخصائص (Attributes/Variables)** التي تحدد حالة الكائن، و**المتغيرات العادية** (المحلية).
  • إنشاء كائنات متعددة من نفس الكلاس والتعامل مع خصائصها ودوالها بشكل مستقل.
  • استخدام **الكونستركتور (Constructor)** لإعطاء قيم أولية للكائنات عند إنشائها.
  • التعرف على دور وأهمية الكلمة المحجوزة **`this`** في سياق الكائنات.
  • البدء في تطبيق مبادئ البرمجة الكائنية في مشاريعك البرمجية.
---

أولاً: مفهوم الكلاس (Class) في جافا

تخيل أنك مهندس معماري وتريد بناء عدة منازل متشابهة. لن تبدأ كل مرة من الصفر في رسم مخطط جديد لكل منزل. بدلاً من ذلك، ستقوم بإنشاء **مخطط (Blueprint)** واحد يحدد جميع التفاصيل: عدد الغرف، مكان الأبواب والنوافذ، نوع المواد، إلخ.

في البرمجة الكائنية، **الكلاس (Class)** هو هذا المخطط. إنه **القالب (Template)** أو **الفئة (Category)** التي تُعرّف الخصائص (ماذا تملك الكائنات؟) والسلوكيات (ماذا تفعل الكائنات؟) التي ستشترك فيها مجموعة من الكائنات المتشابهة.

يحتوي الكلاس عادةً على:

  • **خصائص (Attributes / Fields / Variables):** هذه هي البيانات التي تُعرّف حالة الكائن. مثل: اسم الشخص، عمره، لونه، حجمه.
  • **دوال (Methods):** هذه هي السلوكيات أو الإجراءات التي يمكن للكائن أن يقوم بها. مثل: أن يتكلم الشخص، أن يمشي، أن يأكل.
  • **كونستركتورات (Constructors):** هي دوال خاصة تُستخدم لإنشاء الكائنات من هذا الكلاس وإعطائها قيمًا ابتدائية (سنتحدث عنها لاحقًا).

بشكل أساسي، الكلاس يصف **نوعًا** معينًا من الكائنات. هو ليس الكائن نفسه، بل هو مجرد وصف لكيفية بناء هذا الكائن.

الصيغة العامة لتعريف كلاس:

class ClassName { // اسم الكلاس يبدأ بحرف كبير (convention)
    // خصائص (Attributes / Fields)
    // نوع_بيانات خاصية1;
    // نوع_بيانات خاصية2;

    // كونستركتورات (Constructors) - دوال خاصة لإنشاء الكائن
    // public ClassName(...) { ... }

    // دوال (Methods) - سلوكيات الكائن
    // public void methodName() { ... }
}
مثال: كلاس `Person` (شخص)

هذا الكلاس يصف الخصائص والسلوكيات الأساسية التي يمكن أن يمتلكها أي "شخص":

class Person {
    // الخصائص (Attributes / Fields)
    String name;   // اسم الشخص
    String gender; // جنس الشخص
    String job;    // وظيفته
    int age;       // عمره

    // الدالة (Method) - سلوك يمكن أن يقوم به الشخص
    void printInfo() {
        System.out.println("Name: " + name);
        System.out.println("Gender: " + gender);
        System.out.println("Job: " + job);
        System.out.println("Age: " + age);
    }
}

في هذا المثال، `Person` هو الكلاس. إنه يحدد أن كل كائن من نوع `Person` سيكون لديه `name` و`gender` و`job` و`age`، وسيكون قادرًا على تنفيذ دالة `printInfo()`.

---

ثانياً: مفهوم الكائن (Object) في جافا

إذا كان الكلاس هو المخطط الهندسي للمنزل، فإن **الكائن (Object)** هو المنزل **الحقيقي الملموس** الذي تم بناؤه فعلاً بناءً على هذا المخطط. الكائن هو **نسخة (Instance)** حية ومحددة من الكلاس. كل كائن له خصائصه الخاصة التي تميزه عن غيره من الكائنات المشتقة من نفس الكلاس.

يتم إنشاء الكائن في جافا باستخدام الكلمة المفتاحية **`new`** متبوعة باسم الكونستركتور الخاص بالكلاس.

مثال: إنشاء كائنات من كلاس `Person`
public class Main {
    public static void main(String[] args) {
        // إنشاء كائن جديد اسمه 'ahmad' من الكلاس Person
        Person ahmad = new Person(); 
        
        // تعيين قيم للخصائص الخاصة بالكائن 'ahmad'
        ahmad.name = "أحمد";
        ahmad.gender = "ذكر";
        ahmad.job = "مهندس";
        ahmad.age = 30;

        // استدعاء الدالة printInfo() الخاصة بالكائن 'ahmad'
        System.out.println("معلومات أحمد:");
        ahmad.printInfo();

        System.out.println("\n------------------\n");

        // إنشاء كائن آخر اسمه 'sara' من نفس الكلاس Person
        Person sara = new Person();
        sara.name = "سارة";
        sara.gender = "أنثى";
        sara.job = "طبيبة";
        sara.age = 28;
        
        System.out.println("معلومات سارة:");
        sara.printInfo();
    }
}

// الكلاس Person (يجب أن يكون في نفس الملف أو ملف منفصل Person.java)
class Person {
    String name;
    String gender;
    String job;
    int age;

    void printInfo() {
        System.out.println("Name: " + name);
        System.out.println("Gender: " + gender);
        System.out.println("Job: " + job);
        System.out.println("Age: " + age);
    }
}

الناتج المتوقع:

معلومات أحمد:
Name: أحمد
Gender: ذكر
Job: مهندس
Age: 30

------------------

معلومات سارة:
Name: سارة
Gender: أنثى
Job: طبيبة
Age: 28

في هذا المثال، `ahmad` و `sara` هما كائنان منفصلان، كل منهما يمتلك نسخته الخاصة من الخصائص (`name`, `gender`, `job`, `age`). فـ `ahmad.name` مختلفة عن `sara.name`. لكن كلاهما يستخدم نفس الدالة `printInfo()` المعرفة في الكلاس `Person`.

---

ثالثاً: العلاقة بين الكائن والكلاس

لفهم العلاقة بين الكلاس والكائن بشكل أفضل، يمكننا استخدام التشبيهات التالية:

  • **الكلاس = مخطط أساسي (Blueprint):** يصف كيف يبدو الكائن، ما هي خصائصه، وماذا يمكنه أن يفعل.
    مثال: مخطط سيارة يوضح أنها تحتوي على عجلات، محرك، مقاعد، ويمكنها أن تسير، تتوقف، وتسرع.
  • **الكائن = نسخة من الكلاس (Instance):** هو التنفيذ الفعلي لهذا المخطط.
    مثال: سيارة معينة (مثل "سيارة أحمد" أو "سيارة سارة") تم بناؤها بناءً على مخطط السيارة. كل سيارة لها لونها الخاص، رقم لوحتها، وسرعتها الحالية، ولكنها جميعًا لديها عجلات ومحرك وتعرف كيف تسير.
نقطة مهمة:

الكلاس موجود في مرحلة **التصميم (Design time)** أو **التصنيف (Categorization)**، بينما الكائن موجود في مرحلة **التنفيذ (Runtime)** أو **الواقع (Reality)**.

عند تعديل الكلاس (مثلاً، إضافة خاصية جديدة أو دالة جديدة)، فإن جميع الكائنات التي ستُنشأ لاحقاً من هذا الكلاس **ستَرث** هذه التعديلات تلقائياً. هذا يعكس مرونة البرمجة الكائنية وسهولة التوسع والصيانة.

---

رابعاً: الخصائص (Attributes) والمتغيرات

في جافا، نستخدم مصطلح **خصائص (Attributes)** للإشارة إلى المتغيرات التي تُعرّف داخل الكلاس ولكن خارج أي دالة. هذه الخصائص هي التي تحدد حالة الكائن. هناك أنواع مختلفة من المتغيرات في جافا، ومن المهم التمييز بينها:

  • **متغيرات الكائن (Instance Variables / Attributes / Fields):**
    • هي المتغيرات التي تُعرّف **داخل الكلاس وخارج أي دالة أو كونستركتور**.
    • يمتلك **كل كائن** نسخة خاصة به من هذه المتغيرات. أي أن التغيير في قيمة هذه الخاصية لكائن معين لا يؤثر على قيمة نفس الخاصية في كائن آخر.
    • تمثل حالة الكائن أو بياناته الفريدة.
  • **متغيرات الكلاس (Class Variables / Static Variables):**
    • هي المتغيرات التي تُعرّف **داخل الكلاس باستخدام الكلمة المحجوزة `static`**.
    • يمتلك **الكلاس نفسه** نسخة واحدة فقط من هذا المتغير، ويتم مشاركتها بين **جميع الكائنات** المشتقة من هذا الكلاس. أي أن التغيير في قيمتها من خلال أي كائن أو مباشرة من الكلاس يؤثر على جميع الكائنات.
    • تُستخدم لتمثيل بيانات مشتركة بين جميع كائنات الكلاس.
  • **المتغيرات المحلية (Local Variables):**
    • هي المتغيرات التي تُعرّف **داخل الدوال أو الكونستركتورات أو الكتل البرمجية**.
    • لا يمكن الوصول إليها إلا من داخل الكتلة التي عُرفت فيها.
    • تُستخدم لتخزين بيانات مؤقتة ضرورية لتنفيذ الدالة أو الكونستركتور.
    • لا ترتبط بكائن معين ولا يمكن الوصول إليها من خارجه.
مثال: أنواع المتغيرات المختلفة
public class VariablesTypes {
    // 1. متغير كائن (Instance Variable / Attribute)
    // كل كائن من VariablesTypes سيكون له نسخة خاصة به من 'instanceCounter'
    int instanceCounter;           

    // 2. متغير كلاس (Class Variable / Static Variable)
    // جميع كائنات VariablesTypes ستشترك في نفس النسخة من 'classCounter'
    static int classCounter = 0;    

    // الكونستركتور
    public VariablesTypes() {
        instanceCounter++; // زيادة عداد الكائن الحالي
        classCounter++;    // زيادة عداد الكلاس (يؤثر على جميع الكائنات)
    }

    // دالة (Method)
    public int sum(int x, int y) {
        // 3. متغير محلي (Local Variable)
        // 'z' موجودة فقط داخل دالة sum
        int z = x + y;   
        return z;
    }

    public static void main(String[] args) {
        // إنشاء الكائن الأول
        VariablesTypes obj1 = new VariablesTypes();
        obj1.instanceCounter = 10; // تعيين قيمة لمتغير الكائن لـ obj1
        
        // إنشاء الكائن الثاني
        VariablesTypes obj2 = new VariablesTypes();
        obj2.instanceCounter = 20; // تعيين قيمة لمتغير الكائن لـ obj2

        System.out.println("obj1.instanceCounter: " + obj1.instanceCounter); // 10
        System.out.println("obj2.instanceCounter: " + obj2.instanceCounter); // 20

        // الوصول لمتغير الكلاس مباشرة من اسم الكلاس
        System.out.println("VariablesTypes.classCounter: " + VariablesTypes.classCounter); // 2 (لأننا أنشأنا كائنين)

        // مثال على استخدام دالة مع متغير محلي
        System.out.println("مجموع 5 و 7 هو: " + obj1.sum(5, 7)); // z = 12 داخل الدالة
    }
}
---

خامساً: الكونستركتور (Constructor) في جافا

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

خصائص الكونستركتور:

  • **له نفس اسم الكلاس بالضبط.**
  • **لا يحتوي على نوع إرجاع (No return type)**، ولا حتى `void`.
  • يمكن أن يحتوي على **باراميترات** لاستقبال قيم لتهيئة الخصائص.
  • **إذا لم تكتب أي كونستركتور** في كلاسك، فإن المترجم (Java Compiler) سيضيف لك **كونستركتورًا افتراضيًا (Default Constructor)** بدون باراميترات.

أنواع الكونستركتورات:

1. الكونستركتور الافتراضي (Default Constructor):

هو كونستركتور بدون باراميترات، ويتم توفيره تلقائيًا من قبل جافا إذا لم تُعرف أي كونستركتورات أخرى في الكلاس. يقوم بتهيئة الخصائص بالقيم الافتراضية المذكورة سابقًا (0، 0.0، null، false).

class Person {
    String name;
    int age;

    // الكونستركتور الافتراضي (أو الذي ستكتبه إذا أردت)
    public Person() {
        System.out.println("تم إنشاء كائن Person باستخدام الكونستركتور الافتراضي.");
    }

    void printInfo() {
        System.out.println("Name: " + name + ", Age: " + age);
    }
}

2. الكونستركتور المخصص (Parameterized Constructor):

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

class Person {
    String name;
    int age;

    // كونستركتور مخصص يستقبل الاسم والعمر
    public Person(String n, int a) {
        name = n;    // تعيين قيمة الباراميتر n لخاصية name
        age = a;     // تعيين قيمة الباراميتر a لخاصية age
        System.out.println("تم إنشاء كائن Person باستخدام الكونستركتور المخصص.");
    }

    void printInfo() {
        System.out.println("Name: " + name + ", Age: " + age);
    }
}

مثال استخدام الكونستركتورات:

public class ConstructorExample {
    public static void main(String[] args) {
        // استخدام الكونستركتور المخصص لإعطاء قيم أولية
        Person p1 = new Person("علي", 20); // يتم استدعاء public Person(String n, int a)
        System.out.println("معلومات p1:");
        p1.printInfo();

        System.out.println("\n------------------\n");

        // استخدام الكونستركتور الافتراضي (إذا لم يتم تعريف أي كونستركتورات أخرى)
        // أو الكونستركتور الافتراضي الذي قمنا بتعريفه يدوياً
        Person p2 = new Person(); // يتم استدعاء public Person()
        p2.name = "سارة"; // يمكن تعيين القيم لاحقًا يدويًا
        p2.age = 22;
        System.out.println("معلومات p2:");
        p2.printInfo();
    }
}

// الكلاس Person (يجب أن يكون في نفس الملف أو ملف منفصل Person.java)
class Person {
    String name;
    int age;

    // الكونستركتور الافتراضي
    public Person() {
        System.out.println("تم إنشاء كائن Person باستخدام الكونستركتور الافتراضي.");
    }

    // الكونستركتور المخصص
    public Person(String n, int a) {
        name = n;
        age = a;
        System.out.println("تم إنشاء كائن Person باستخدام الكونستركتور المخصص.");
    }

    void printInfo() {
        System.out.println("Name: " + name + ", Age: " + age);
    }
}

الناتج المتوقع:

تم إنشاء كائن Person باستخدام الكونستركتور المخصص.
معلومات p1:
Name: علي, Age: 20

------------------

تم إنشاء كائن Person باستخدام الكونستركتور الافتراضي.
معلومات p2:
Name: سارة, Age: 22
---

سادساً: الكلمة `this`

الكلمة المحجوزة **`this`** في جافا هي مرجع (reference) يشير إلى **الكائن الحالي (Current Object)** الذي يتم تنفيذ الكود داخله. تُستخدم بشكل أساسي داخل دوال الكلاس والكونستركتورات.

أهم استخدامات `this`:

  • **التمييز بين خصائص الكلاس وباراميترات الدالة/الكونستركتور:** يحدث هذا غالبًا عندما يكون لاسم الباراميتر نفس اسم خاصية الكلاس. استخدام `this.propertyName` يشير صراحةً إلى خاصية الكائن، بينما `propertyName` لوحدها تشير إلى الباراميتر.
  • **استدعاء كونستركتور آخر من نفس الكلاس:** يمكن لكونستركتور واحد أن يستدعي كونستركتورًا آخر باستخدام `this(...)`. (مفهوم متقدم قليلاً).

1. التمييز بين الخصائص والباراميترات (الأكثر شيوعًا):

مثال صحيح باستخدام `this` (موصى به):
class Employee {
    String name;
    int id;

    // هنا، باراميترات الكونستركتور لها نفس أسماء الخصائص
    public Employee(String name, int id) {
        // 'this.name' تشير إلى خاصية الكائن 'name'
        // 'name' بدون this تشير إلى باراميتر الكونستركتور
        this.name = name;  
        this.id = id;
    }

    void printDetails() {
        System.out.println("Employee Name: " + this.name + ", ID: " + this.id);
    }
}

public class ThisKeywordExample {
    public static void main(String[] args) {
        Employee emp1 = new Employee("محمد", 101);
        emp1.printDetails(); // الناتج: Employee Name: محمد, ID: 101
    }
}
مثال خاطئ بدون `this` (يؤدي إلى أخطاء منطقية):

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

class BadEmployee {
    String name; // خاصية
    int id;      // خاصية

    public BadEmployee(String name, int id) {
        name = name;  // هنا، أنت تسند قيمة الباراميتر 'name' إلى نفسه!
        id = id;      // ونفس الشيء للـ 'id'
    }

    void printDetails() {
        System.out.println("Employee Name: " + name + ", ID: " + id);
    }
}

public class BadThisExample {
    public static void main(String[] args) {
        BadEmployee emp2 = new BadEmployee("علي", 202);
        emp2.printDetails(); // الناتج سيكون: Employee Name: null, ID: 0 (القيم الافتراضية)
    }
}

في المثال الخاطئ، لم يتم تعيين قيم "علي" و 202 لخصائص الكائن `name` و `id`، بل تم إسناد قيم الباراميترات إلى نفسها، تاركةً خصائص الكائن بالقيم الافتراضية (`null` للنص و `0` للعدد الصحيح). استخدام `this` يحل هذه المشكلة بوضوح.

---

سابعاً: تدريب عملي: بناء كلاس `Car`

حان الوقت لتطبيق ما تعلمته عملياً! أنشئ كلاسًا جديدًا يمثل كيان "السيارة".

المطلوب:

أنشئ كلاس اسمه Car يحتوي على ما يلي:

  • **خصائص (Attributes):**
    • `brand` (نوع `String`): لاسم ماركة السيارة (مثال: Toyota, Mercedes).
    • `model` (نوع `String`): لاسم موديل السيارة (مثال: Camry, C-Class).
    • `year` (نوع `int`): لسنة صنع السيارة (مثال: 2020, 2023).
  • **كونستركتور (Constructor):**
    • كونستركتور يستقبل ثلاث باراميترات: الماركة، الموديل، والسنة.
    • استخدم الكلمة المحجوزة `this` للتمييز بين أسماء الباراميترات وخصائص الكلاس.
  • **دالة (Method):**
    • دالة اسمها printCarInfo() (ترجع `void`) وظيفتها طباعة جميع معلومات السيارة (الماركة، الموديل، السنة) على سطر واحد أو عدة أسطر بشكل منسق.

بعد إنشاء كلاس Car، انتقل إلى كلاس Main (أو أي كلاس آخر يحتوي على دالة `main`) وقم بـ:

  • **إنشاء 3 كائنات مختلفة** من كلاس Car.
  • عند إنشاء كل كائن، قم بتمرير بيانات مختلفة (ماركة، موديل، سنة) للكونستركتور.
  • لكل كائن من الكائنات الثلاثة، قم بـ **استدعاء دالة printCarInfo()** لعرض بياناته.

توقع الناتج: يجب أن ترى معلومات ثلاث سيارات مختلفة مطبوعة على الكونسول.

// كلاس Car
class Car {
    // الخصائص
    // ...

    // الكونستركتور
    // ...

    // الدالة
    // ...
}

public class MainCarExample {
    public static void main(String[] args) {
        // أنشئ 3 كائنات من Car هنا
        // مثال: Car car1 = new Car("...", "...", ...);
        // ثم استدعِ دالة printCarInfo() لكل كائن
        // example: car1.printCarInfo();
    }
}
---

ثامناً: ملخص الوحدة

لقد وصلتَ بنجاح إلى نهاية هذه الوحدة الهامة، واكتسبتَ أساسيات البرمجة الكائنية التوجه في جافا. إليك أبرز النقاط التي تعلمتها:

  • **الكلاس (Class):** هو القالب أو المخطط الذي يصف الخصائص (البيانات) والدوال (السلوكيات) المشتركة لمجموعة من الكائنات.
  • **الكائن (Object):** هو نسخة حية وملموسة من الكلاس، ويمتلك نسخته الخاصة من الخصائص. يتم إنشاؤه باستخدام الكلمة المفتاحية `new`.
  • **الخصائص (Attributes):** هي متغيرات تُعرف داخل الكلاس (خارج أي دالة) وتُعرّف حالة الكائن. يجب التمييز بينها وبين متغيرات الكلاس (`static`) والمتغيرات المحلية (داخل الدوال).
  • **الكونستركتور (Constructor):** دالة خاصة لها نفس اسم الكلاس، تُستدعى تلقائيًا عند إنشاء الكائن لتهيئة خصائصه بقيم أولية. يمكن أن يكون هناك كونستركتور افتراضي (بدون باراميترات) أو كونستركتورات مخصصة (مع باراميترات).
  • **الكلمة `this`:** تُستخدم للإشارة إلى الكائن الحالي، وهي مفيدة بشكل خاص للتمييز بين خصائص الكائن والباراميترات المحلية في الدوال والكونستركتورات عندما تتشابه الأسماء.

تُعد البرمجة الكائنية فلسفة قوية ستمكنك من بناء برامج منظمة، قابلة للتوسع، وإعادة الاستخدام بشكل لم يكن ممكنًا باستخدام البرمجة الإجرائية فقط. هذه الوحدة هي نقطة البداية لمفاهيم أكثر عمقًا في OOP مثل الوراثة (Inheritance) والتغليف (Encapsulation) وتعدد الأشكال (Polymorphism)، والتي ستتعرف عليها في مستويات متقدمة من دراستك للبرمجة.

في هذه المرحلة، ستكون قد أتممت معظم وحدات المقرر الأساسية. الوحدة القادمة ستكون هي **الوحدة الختامية** حيث سنقوم بملخص شامل لكل ما تعلمته وتقديم بعض التوصيات لمواصلة رحلتك في عالم البرمجة والأمن السيبراني.

---

تاسعاً: المراجع

  • موقع Oracle Java Documentation.
  • "Java: A Beginner's Guide" – Herbert Schildt.
  • "Head First Java" – Kathy Sierra, Bert Bates.