التجريد في جافا | Java Abstraction

مفهوم الـ Abstraction

تجريد: تعني Abstraction في اللغة الإنجليزية, و هو أسلوب مهم جداً يستخدم لتسهيل كتابة الأوامر على المبرمجين, فهو يجعلك قادراً على تنفيذ ما تريد دون الحاجة إلى معرفة كافة التفاصيل التي تم فيها تنفيذ ذلك. إذاً الـ Abstraction يجعلك تتعامل مع الأشياء بسطحية بدل أن تغوص في معرفة تفاصيل الكودات المعقدة.

فمثلاً إذا كنت تنوي بناء برنامج يتيح لمستخدميه إرسال إقتراحات حول التطبيق من خلال البريد الإلكتروني, في هذه الحالة لن يكون عليك القلق بتاتاً حول طريقة تعامل أوامر جافا مع البروتوكولات التي تعمل عندما يتم إرسال رسائل إلكترونية من خلال هذا التطبيق, لأنك لست مسؤولاً عنها, فعلياً هذه وظيفة شركة Sun التي تقوم بتطوير لغة جافا. و هم يخبرونك أنه لإرسال بريد إلكتروني إستخدم هذه الأوامر البسيطة فقط.

إذاً الـ Abstraction هو أسلوب يستخدم لإخفاء تفاصيل تنفيذ البرنامج. لتطبيق مفهوم الـ Abstraction نستخدم الكلمة abstract ضمن شروط محددة.


مصطلحات تقنية

إذا أردت تعريف الشيء كـ abstract, أكتب فقط الكلمة abstract قبله.

  • الكلاس المعرف كـ abstract يسمى Abstract Class.

  • الكلاس العادي الغير معرف كـ abstract يسمى Concrete Class.

  • الدالة المعرفة كـ abstract تسمى Abstract Method أو Abstract Function.

مفهوم Abstract Class

إذا وضعت الكلمة abstract قبل إسم الكلاس, ستتغير قليلاً طريقة التعامل معه لأنه لم يعد كلاس عادي. و عندها سيراه المترجم كـ Abstract Class.


نقاط مهمة حول الـ Abstract Class

  • الكلاس العادي لا يمكنه أن يحتوي على دوال نوعها abstract.

  • الـ Abstract Class يمكنه أن يحتوي على دوال عادية, و يمكنه أن يحتوي على دوال نوعها abstract.

  • إذا قمت بتعريف الكلاس كـ abstract, فهذا يعني أن هذا الكلاس لا يمكن إنشاء كائنات منه.

  • بما أنه لا يمكن إنشاء كائنات من Abstract Class, فهذا يعني أنه للإستفادة من هذا الكلاس, يجب وراثته.

  • الكلاس الذي يرث من كلاس نوعه abstract, يجب أن يفعل Override لجميع الدوال المعرفة كـ abstract.



طريقة تعريف كلاس نوعه abstract

نكتب فقط الكلمة abstract قبل الكلمة class.

مثال

abstract class Example {

} 
		

مفهوم Abstract Method

إذا كنت تريد بناء دالة و جعل الكلاس الذي يرثها هو المسؤول عن كتابتة محتواها, قم بتعريفها كـ abstract.


نقاط مهمة حول الـ Abstract Method

  • إذا وضعت الكلمة abstract قبل إسم الدالة, فهذا يعني أنها دالة من النوع abstract.

  • الدالة التي نوعها abstract هي دالة لها إسم و نوع محدد, لكنها لا تحتوي على body (جسم), أي لا تملك أقواس بداية و نهاية { }.

  • الدالة العادية تحتوي على أقواس البداية و النهاية { }.

  • الـ Abstract Method يجب وضع فاصلة منقوطة ; في آخرها بدل أقواس البداية و النهاية.

  • الكلاس الذي يرث من كلاس نوعه abstract, يجب أن يفعل Override لكل دوالة نوعها abstract, أي يجب أن يكتب الـ body لهذه الدوال.



طريقة تعريف دوال نوعها abstract

نكتب فقط الكلمة abstract بعد Modifier الدالة.

أمثلة

public abstract void print();

public abstract int getId();

public abstract void setId(int id); 
		


إنتبه: عندما تفعل Override لدالة نوعها abstract, يجب أن لا تعرفها كـ abstract من جديد.

مثال

لنفترض أننا قمنا بتعريف دالة إسمها displayMessage() كـ abstract.

public abstract void displayMessage(); 
		

لا يجب أن نكتب abstract عندما تفعل لها Override.

@Override
public abstract void displayMessage() {       // abstract methods cannot have a body <-- abstract="" pre="">
		

لتصحيح هذا الخطأ, نمسح فقط الكلمة abstract.

@Override
public void displayMessage() {               // نلاحظ أن التنبيه إختفى

} 
		

أمثلة شاملة حول التجريد في جافا 

وضعنا هنا ثلاثة أمثلة بسيطة و مترابطة تعلمك أساسيات التجريد في جافا .


الآن سنقوم بتعريف كلاس إسمه A, نوعه abstract, يملك متغير إسمه x, و دالة إسمها print().
بعدها سنقوم ببناء الكلاس Main لتجربة الكود.

المثال الأول

A.java
                    public abstract class A {     // إذاً لا يمكن إنشاء كائنات منه ,abstract نوعه A الكلاس

                    int x;

                    public void print() {
                    System.out.println("This is just an example.");
                    }

                    }
                  

Main.java
                    public class Main {

                    public static void main(String[] args) {

                    A a = new A();     // Incompatible Type: abstraction.A is abstract; cannot be instantiated <-- pre="">
                  

سنحصل على النتيجة التالية عند التشغيل.

Exception in thread "main" java.lang.RuntimeException:
Uncompilable source code - abstraction.A is abstract; cannot be instantiated

سبب الخطأ هنا أننا حاولنا إنشاء كائن من كلاس نوعه abstract.

الآن سنقوم بإنشاء كلاس جديد إسمه B, يرث من الكلاس A.
بداخل الكلاس Main سننشئ كائن من الكلاس B لنحصل على الأشياء الموجودة في الكلاس A.


المثال الثاني

A.java
                    public abstract class A {

                    int x;

                    public void print() {
                    System.out.println("This is just an example.");
                    }

                    }
                  

B.java
                    public class B extends A {     // إذاً سيرث كل شيء موجود فيه .A يرث من الكلاس B هنا قلنا أن الكلاس

                    }
                  

Main.java
                    public class Main {

                    public static void main(String[] args) {

                    B b = new B();     // B هنا قمنا بإنشاء كائن من الكلاس

                    b.print();         // A من الكلاس B التي ورثها الكلاس print() هنا قمنا باستدعاء الدالة

                    b.x = 10;          // A من الكلاس B الذي ورثه الكلاس x هنا قمنا بتغيير قيمة المتغير

                    System.out.println("b.x contain: " + b.x);       // x هنا قمنا بعرض قيمة المتغير
                    }

                    }
                  

سنحصل على النتيجة التالية عند التشغيل.

                    This is just an example.
                    b.x contain: 10
                  

إذاً بما أننا لا نستطيع إنشاء كائن مباشرةً من الكلاس A, قمنا بإنشاء الكلاس B بهدف وراثة الأشياء الموجودة في الكلاس A, و بعدها أنشأنا كائن من الكلاس B و تأكدنا أنه يحتوي على جميع الأشياء الموجودة في الكلاس A.

الآن سنقوم بإضافة دالتين نوعهما abstract في الكلاس A, ثم سنفعل لهما Override في الكلاس B.
بداخل الكلاس Main سننشئ كائن من الكلاس B و سنقوم باستدعاء الدوال الجديدة التي قمنا بإضفاتها.


المثال الثالث

A.java
                    public abstract class A {

                    int x;

                    public void print() {
                    System.out.println("This is just an example.");
                    }

                    public abstract void setX(int x);     // كل كلاس يرثها Override إذاً يجب أن يفعل لها <-- abstract هنا قمنا بتعريف دالة نوعها

                    public abstract int getX();           // كل كلاس يرثها Override إذاً يجب أن يفعل لها <-- abstract هنا قمنا بتعريف دالة نوعها

                    }
                  

B.java
                    public class B extends A {     // abstract لأي دالة سيرثها من النوع Override يجب أن يفعل A يرث من الكلاس B بما أن الكلاس

                    // setX() للدالة Override هنا فعلنا
                    @Override
                    public void setX(int x) {
                    super.x = x;
                    }

                    // getX() للدالة Override هنا فعلنا
                    @Override
                    public int getX() {
                    return super.x;
                    }

                    }
                  

Main.java
                    public class Main {

                    public static void main(String[] args) {

                    B b = new B();     // B هنا قمنا بإنشاء كائن من الكلاس

                    b.print();         // A من الكلاس B التي ورثها الكلاس print() هنا قمنا باستدعاء الدالة

                    b.setX(55);        // setX() عن طريق الدالة A من الكلاس B الذي ورثه الكلاس x هنا قمنا بتغيير قيمة المتغير

                    System.out.println("b.x contain: " + b.getX());       // getX() عن طريق الدالة x هنا قمنا بعرض قيمة المتغير
                    }

                    }
                  

وضعنا هنا مثال بسيط حول Abstract Class يرث من Abstract Class

سنحصل على النتيجة التالية عند التشغيل.

                    This is just an example.
                    b.x contain: 55
                  


لآن سنقوم بتعريف كلاس إسمه A نوعه abstract, يملك متغير إسمه x, و دالة إسمها print1() نوعها abstract.
بعدها سنقوم بتعريف كلاس إسمه B نوعه abstract يرث من الكلاس A, يملك دالة إسمها print2() نوعها abstract.
بعدها سنقوم بتعريف كلاس إسمه C, يرث من الكلاس B.
في الأخير سنقوم ببناء الكلاس Main لتجربة الكود.


A.java
                    public abstract class A {

                    int x;

                    public abstract void print1();

                    }
                  

B.java
                    public abstract class B extends A {

                    public abstract void print2();

                    // print1() أيضاً ورث الدالة B لا تنسى أن الكلاس

                    }
                  

C.java
                    public class C extends B {

                    // B التي ورثها من الكلاس abstract لجميع الدوال التي نوعها Override يجب أن يفعل C الكلاس

                    @Override
                    public void print1() {
                    System.out.println("Class C should override the method print1()");
                    }

                    @Override
                    public void print2() {
                    System.out.println("Class C should override the method print2()");
                    }

                    }
                  

Main.java
                    public class Main {

                    public static void main(String[] args) {

                    C c = new C();     // C هنا قمنا بإنشاء كائن من الكلاس

                    c.print1();        // Override و فعل لها C التي ورثها الكلاس print1() هنا قمنا باستدعاء الدالة
                    c.print2();        // Override و فعل لها C التي ورثها الكلاس print2() هنا قمنا باستدعاء الدالة

                    }

                    }
                  

سنحصل على النتيجة التالية عند التشغيل.

                    Class C should override the method print1()
                    Class C should override the method print2() 
                  


وضعنا هنا مثال مهم يعلمك متى تحتاج إلى إنشاء Abstract Class

الآن سنقوم ببناء كلاس إسمه Person نوعه abstract, يملك أربعة خصائص نوعهم private إسمهم name, gender, brithday و isMarried, و يملك دوال Setter and Getter لهذه الخصائص, و يملك أيضاً دالة إسمها displayInfo() نوعها abstract.

بعدها سنقوم بتعريف كلاس إسمه Student يرث من الكلاس Person و يملك متغير إضافي إسمه specialization.
بعدها سنقوم بتعريف كلاس إسمه Employee يرث من الكلاس Person و يملك متغير إضافي إسمه workPlace.
الفكرة هنا أن أي كلاس سيتم إنشاءه لتمثيل إنسان يجب أن يرث من الكلاس Person الذي يملك الخصائص المشتركة لكل البشر.

في الأخير سنقوم ببناء الكلاس Main لتجربة الكود.


Person.java
                    public abstract class Person {

                    // هنا قمنا بتعريف الخصائص المشتركة لدى جميع البشر
                    private String name;
                    private String gender;
                    private String birthday;
                    private boolean isMarried;

                    // هنا قمنا بتعريف الكونستركتور
                    public Person(String n, String g, String b, boolean i) {
                    name = n;
                    gender = g;
                    birthday = b;
                    isMarried = i;
                    }

                    // Getter هنا قمنا بتعريف دوال الـ

                    public String getName() {
                    return name;
                    }

                    public String getGender() {
                    return gender;
                    }

                    public String getBirthday() {
                    return birthday;
                    }

                    public boolean getIsMarried() {
                    return isMarried;
                    }

                    // Setter هنا قمنا بتعريف دوال الـ
                    public void setName(String n) {
                    name = n;
                    }

                    public void setGender(String g) {
                    gender = g;
                    }

                    public void setBirthday(String b) {
                    birthday = b;
                    }

                    public void setIsMarried(boolean i) {
                    isMarried = i;
                    }

                    // abstract و التي نوعها displayInfo() هنا قمنا بتعريف الدالة
                    public abstract void displayInfo();

                    }
                  

Student.java
                    public class Student extends Person {

                    String specialization;     // هنا قمنا بتعريف خاصية التخصص و التي يملكها فقط التلاميذ

                    // هنا قمنا بتعريف الكونستركتور
                    public Student(String n, String g, String b, boolean i, String s) {
                    super(n, g, b, i);       // Person هنا سيتم إرسال أول أربع قيم إلى كونستركتور الكلاس
                    specialization = s;
                    }

                    // بشكل ملائم للتلامذة displayInfo() للدالة Override هنا فعلنا
                    @Override
                    public void displayInfo() {
                    System.out.println("Name: " + getName());
                    System.out.println("Gender: " + getGender());
                    System.out.println("Birthday: " + getBirthday());
                    System.out.println("Specialization: " + specialization);
                    System.out.println("---------------------------------");
                    }

                    }
                  

Employee.java
                    public class Employee extends Person {

                    String workPlace;     // هنا قمنا بتعريف خاصية مكان العمل و التي يملكها فقط الموظفون و العمال

                    // هنا قمنا بتعريف الكونستركتور
                    public Employee(String n, String g, String b, boolean i, String w) {
                    super(n, g, b, i);       // Person هنا سيتم إرسال أول أربع قيم إلى كونستركتور الكلاس
                    workPlace = w;
                    }

                    // بشكل ملائم للموظفين أو العمال displayInfo() للدالة Override هنا فعلنا
                    @Override
                    public void displayInfo() {
                    System.out.println("Name: " + getName());
                    System.out.println("Gender: " + getGender());
                    System.out.println("Birthday: " + getBirthday());

                    if(getIsMarried() == true) {
                    System.out.println("is Married: yes");
                    }
                    else {
                    System.out.println("is Married: no");
                    }

                    System.out.println("Work place: " + workPlace);
                    System.out.println("---------------------------------");
                    }

                    }
                  

Main.java
                    public class Main {

                    public static void main(String[] args) {

                    // و هو عبارة عن إنسان له خصائص طالب Student هنا قمنا بإنشاء كائن من الكلاس
                    Student s = new Student("Mhamad", "Male", "1994", false, "Computer Science");
                    s.displayInfo();

                    // و هو عبارة عن إنسان له خصائص موظف أو عامل Employee هنا قمنا بإنشاء كائن من الكلاس
                    Employee e = new Employee("Rana", "Female", "1986", true, "Al-Iman school");
                    e.displayInfo();

                    }

                    }
                  

سنحصل على النتيجة التالية عند التشغيل.

                    Name: Mhamad
                    Gender: Male
                    Birthday: 1994
                    Specialization: Computer Science
                    ---------------------------------
                    Name: Rana
                    Gender: Female
                    Birthday: 1986
                    is Married: yes
                    Work place: Al-Iman school
                    --------------------------------- 
              

سترى فائدة الـ Abstraction أيضاً في دروس متقدمة عندما تستخدم كلاسات جاهزة تتيح لك التعامل مع الشبكات (Networks), الواجهات (GUI) و قواعد البيانات (DataBases) بكل سهولة. كما أنك ستراها عندما تعمل على بناء مشاريع كبيرة, تجبرك على إستخدام هذا الأسلوب لتسهيل العمل في المشروع.