الفئة المجردة و الواجهة لجافا
الفئة المجردة لجافا 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
13abstract 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
25abstract 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
41public 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"
يوجد أدناه الفئة نقطة "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
41public 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
185interface 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 methodC1 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 B1I am in methodI2 of class B1
var2 : I am in methodC1 of class A1
var3 : I am in methodC1 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