الفئة المجردة لجافا Abstract Class in java
تستخدم الفئات المجردة لجافا "Java Abstract classes" للإعلان عن الخصائص المشتركة للفئات الفرعية "subclasses". الفئة المجردة لا يمكن أن تكون فئة مثيل "Instance". لا يمكن استخدامها إلا بوصفها الفئة المتفوقة "superclass" للفئات الأخرى الاتي إمتددن من الفئة المجردة. يتم تعريف الفئات المجردة بالكلمة المجردة "abstract keyword". وتستخدم الفئات المجردة لتوفير قالب أو تصميم لفئات فرعية محددة أسفل شجرة الميراث "inheritance tree".
مثل أي فئة أخرى، يمكن لفئة مجردة أن تحتوي على حقول التي تصف الخصائص والأساليب التي تصف الإجراءات التي يمكن أن تؤديها الفئة. يمكن لفئة مجردة "abstract class" أن تشمل المناهج التي لا تحتوي على التنفيذ "implementation". وتسمى هذه بالمناهج التجريدية "abstract methods". يجب الإعلان نهاية المنهج التجريدي بفاصلة منقوطة ";" "semicolon" بدلا من كتلة. إذا كانت الفئة لديها أي منهج مجرد "abstract methods"، سواء المعلنة أو الموروثة، فيجب أن تعلن الفئة كاملة كفئة مجردة. و تستخدم المناهج التجريدية "abstract methods" لتقديم نموذج للفئات التي ترث المنهج المجرد "abstract methods".
لا يمكن أن يكون للفئات المجردة مثيل، ويجب أن يكونوا كفئات فرعية ، ويجب توفير التطبيقات الفعلية للمناهج التجريدية "abstract methods". و بطبيعة الحال يمكن ان يتم تجاوز أي تنفيذ محدد، من قبل فئات فرعية إضافية "subclasses". يجب أن يكون للكائن تنفيذ لجميع مناهجه. تحتاج إلى إنشاء فئة فرعية لتوفر تنفيذ للمنهج التجريدي "abstract method".
ويمكن لفئة مجردة تسمى بــ "Vehicle" ان يتم تحديدها مجردة لتمثيل التجريد العام لوسيلة نقل "Vehicle"،كما ان خلق مثيلات "instances" من الفئة لا يكون ذا مغزى.
1 2 3 4 5 6 7 8 9 10 11 12 13 | abstract class Vehicle { int numofGears; String color; abstract boolean hasDiskBrake(); abstract int getNoofGears(); } |
مثال على فئة تسمى شكل "shape" تعتبر كفئة مجردة
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | abstract class Shape { public String color; public Shape() { } public void setColor(String c) { color = c; } public String getColor() { return color; } abstract public double area(); } |
يمكننا أيضا تنفيذ الفئة أشكال عامة "shapes" كفئة مجردة حتى نتمكن من رسم خطوط ودوائر ومثلثات الخ ، جميع الأشكال لها بعض الحقول المشتركة و المناهج ، ولكن لكل واحد منها ، بطبيعة الحال ،المزيد من الحقول و المناهج. الفئة مجردة تضمن بأن يكون لكل شكل نفس الخصائص الأساسية. ونقوم بإعلان هذه الفئة مجردة لأنه لا يوجد شيء يمثل شكل عام. يمكن أن يكون هناك فقط الأشكال الملموسة مثل مربعات مثلثات ودوائر الخ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | public class Point extends Shape { static int x, y; public Point() { x = 0 ; y = 0 ; } public double area() { return 0 ; } public double perimeter() { return 0 ; } public static void print() { System.out.println( "point: " + x + "," + y); } public static void main(String args[]) { Point p = new Point(); p.print(); } } |
Output
point: 0, 0
لاحظ أنه ، من أجل إنشاء كائن نقطة "Point" ، ففئتها لا يمكن أن تكون مجردة. هذا يعني أن كل المناهج المجردة للفئة شكل "Shape" يجب أن تنفذ من قبل الفئة نقطة "Point".
يجب أن تُعرِف الفئة الفرعية التنفيذ لكل منهج تجريدي "abstract method" للفئة المتفوقة المجردة ، أو الفئة الفرعية نفسها سوف تكون أيضا مجردة. وبالمثل يمكن أن يتم إنشاء كائنات أخرى باستخدام الشكل العام للفئة التجريدية.
العيب الكبير في استخدام الفئات المجردة هو عدم الفدرة على استخدام وراثة متعددة. بمعنى، عندما تمتد فئة من فئة مجردة، فإنه لا يمكنها تمديد أية فئة أخرى.
واجهة جافا Java Interface
في جافا، يتم حل مشكلة الوراثة "inheritance" المتعددة بإستعمال بناء قوي يسمى الواجهات "interfaces". ويمكن استخدام واجهة لتحديد القالب العام "generic template" ومن ثم فئة مجردة أو أكثر لتحديد التطبيقات جزئية للواجهة. الواجهات تحدد فقط الإعلان عن المنهج "method" (العام و الضمني و المجردة "implicitly public and abstract")، ويمكن أن تحتوي فقط على الحقول (التي هي ضمنية ، العامة ، نهائية ، ثابتة "implicitly public static final"). تعريف الواجهة يبدأ مع الكلمة الرئيسية واجهة "interface". واجهة لفئة مجردة مثل هذه لا يمكن إنشاء مثيل لها "instantiated".
الوراثة المتعددة "Multiple Inheritance" يسمح بها عند تمديد الواجهات. واجهة واحدة يمكن أن تمتد الى واجهة أو أكثر. جافا لا تدعم الوراثة متعددة "Multiple Inheritance" ، ولكنها تسمح لك بتمديد "extend" فئة واحدة وتنفيذ العديد من الواجهات.
إذا كانت الفئة التي تُطبق الواجهة لا تُعَرِّفْ جميع المناهج للواجهة، فيجب أن يتم إعلانها مجردة و تعريفات المناهج يجب ان توفيرها الفئة فرعية الاتي يمتددن من الفئة مجردة.
المثال 1 : فيما يلي مثال لواجهة بإسم شكل "interface"
1 2 3 4 5 6 7 8 9 | interface Shape { public double area(); public double volume(); } |
يوجد أدناه الفئة نقطة "Point class" التي تطبق الواجهة الشكل "Shape interface".
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | public class Point implements Shape { static int x, y; public Point() { x = 0 ; y = 0 ; } public double area() { return 0 ; } public double volume() { return 0 ; } public static void print() { System.out.println( "point: " + x + "," + y); } public static void main(String args[]) { Point p = new Point(); p.print(); } } |
على نحو مماثل، يمكن إنشاء الكائنات شكل الأخرى بإستعمال واجهة برمجة "interface programming" عن طريق تنفيذ واجهة شكل عامة .
مثال 2 : وفيما يلي برنامج لواجهات جافا "java interfaces program" يعرض قوة برمجة الواجهة "interface programming" لجافا
القائمة أدناه تضهر لنا 2 من الواجهات و 4 فئات واحدة منهم فئة مجردة.
ملاحظة : المنهج toString في الفئة A1 هو نسخة تجاوزناها للمنهج الذي تم تحديده في فئة تسمى كائن "Object". الفئات B1 و C1 تلبي متطلبات عقد الواجهة. ولكن بما أن الفئة D1 لا تقوم بتعريف كافة المناهج لتطبيق الواجهة I2 ، فإن الفئة D1 أعلنت مجردة.
أيضا ،
تُنتج "i1.methodI2" خطأ في عملية التحويل البرمجي "compilation" لأن هذا المنهج لم يعلن في I1 أو أي واحدة من الواجهات الفائقة لها هذا إذا كانت موجودة. بالتالي المسبل او ما يسمى بالداونكسة "downcast" لمرجعية الواجهة I1 يحل المشكلة كما تظهر في البرنامج. المشكلة نفسها تنطبق على "i1.methodA1" ، و تحل ايضا عن طريق المسبل او ما يسمى بالداونكسة "downcast".
عندما نستدعي المنهج toString () الذي هو منهج للفئة كائن "Object"، لا يبدو أن هناك أي مشكلة باعتبار أن كل واجهة أو فئة تمتد من كائن "Object" و أي فئة يمكن ان تتجاوز toString () الافتراضي لتتناسب مع احتياجات التطبيق الخاص بك. ((C1) O1). methodI1 () لها تحويل برمجي ناجح ، ولكن يُنْتِجْ ClassCastException في وقت التشغيل "runtime". هذا لأن B1 ليس لديها أي علاقة مع C1 إلا أنهم "الأشقاء". لا يمكنك قولبة الأشقاء واحدا في الآخر.
عندما يتم استدعاء منهج واجهة معينة على مرجع معين ، السلوك الذي سوف ينتج سيكون مناسب للفئة التي منها تم إنشاء هذا الكائن المعينة. هذا هو تعدد أشكال وقت التشغيل "runtime polymorphism" استنادا إلى الواجهات و هيمنة المناهج.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | interface I1 { void methodI1(); // public static by default } interface I2 extends I1 { void methodI2(); // public static by default } class A1 { public String methodA1() { String strA1 = "I am in methodA1 of class A1" ; return strA1; } public String toString() { return "toString() method of class A1" ; } } class B1 extends A1 implements I2 { public void methodI1() { System.out.println( "I am in methodI1 of class B1" ); } public void methodI2() { System.out.println( "I am in methodI2 of class B1" ); } } class C1 implements I2 { public void methodI1() { System.out.println( "I am in methodI1 of class C1" ); } public void methodI2() { System.out.println( "I am in methodI2 of class C1" ); } } // Note that the class is declared as abstract as it does not // satisfy the interface contract abstract class D1 implements I2 { public void methodI1() { } // This class does not implement methodI2() hence declared abstract. } public class InterFaceEx { public static void main(String[] args) { I1 i1 = new B1(); i1.methodI1(); // OK as methodI1 is present in B1 // i1.methodI2(); Compilation error as methodI2 not present in I1 // Casting to convert the type of the reference from type I1 to type I2 ((I2) i1).methodI2(); I2 i2 = new B1(); i2.methodI1(); // OK i2.methodI2(); // OK // Does not Compile as methodA1() not present in interface reference I1 // String var = i1.methodA1(); // Hence I1 requires a cast to invoke methodA1 String var2 = ((A1) i1).methodA1(); System.out.println( "var2 : " + var2); String var3 = ((B1) i1).methodA1(); System.out.println( "var3 : " + var3); String var4 = i1.toString(); System.out.println( "var4 : " + var4); String var5 = i2.toString(); System.out.println( "var5 : " + var5); I1 i3 = new C1(); String var6 = i3.toString(); System.out.println( "var6 : " + var6); // It prints the Object toString() method Object o1 = new B1(); // o1.methodI1(); does not compile as Object class does not define // methodI1() // To solve the probelm we need to downcast o1 reference. We can do it // in the following 4 ways ((I1) o1).methodI1(); // 1 ((I2) o1).methodI1(); // 2 ((B1) o1).methodI1(); // 3 /* * * B1 does not have any relationship with C1 except they are "siblings". * * Well, you can't cast siblings into one another. * */ // ((C1)o1).methodI1(); Produces a ClassCastException } } |
Output
I am in methodI1 of class B1
I am in methodI2 of class B1
I am in methodI1 of class B1
I am in methodI2 of class B1
var2 : I am in methodA1 of class A1
var3 : I am in methodA1 of class A1
var4 : toString() method of class A1
var5 : toString() method of class A1
var6 : C1@190d11
I am in methodI1 of class B1
I am in methodI1 of class B1
I am in methodI1 of class B1
ليست هناك تعليقات:
إرسال تعليق