3. الفئات، والهياكل، والواجهات
3.1. الكائن من خلال المثال
3.1.1. معلومات عامة
سنناقش الآن، من خلال مثال، البرمجة الكائنية. الكائن هو كيان يحتوي على بيانات تحدد حالته (تسمى «الخصائص») ووظائف (تسمى «الطرق»). يتم إنشاء الكائن وفقًا لنموذج يُسمى «الفئة»:
Public Class c1
' السمات
Private p1 As type1
Private p2 As type2
....
' الطريقة
Public Sub m1(....)
...
End Sub
' طريقة
Public Function m2(...)
....
End Function
End Class
انطلاقًا من الفئة C1 السابقة، يمكن إنشاء العديد من الكائنات O1، O2،... وستكون لجميعها الخصائص p1، p2،... والطرق m3، m4،... لكن قيم خصائصها ستكون مختلفة، بحيث يكون لكل منها حالته الخاصة. وبالمثل، فإن الإعلان
يُنشئ كائنين (المصطلح غير صحيح هنا) من النوع (الفئة) Integer. خاصيتهما الوحيدة هي قيمتهما. إذا كان O1 كائنًا من النوع C1، فإن O1.p1 تشير إلى الخاصية p1 الخاصة بـ O1، بينما تشير O1.m1 إلى الطريقة m1 الخاصة بـ O1. لنفترض نموذجًا أوليًا للكائن: الفئة personne.
3.1.2. تعريف فئة «شخص»
سيكون تعريف الفئة personne كما يلي:
Public Class personne
' السمات
Private prenom As String
Private nom As String
Private age As Integer
' الطريقة
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' طريقة
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
لدينا هنا تعريف لفئة، أي لنوع من أنواع البيانات. وعندما نقوم بإنشاء متغيرات من هذا النوع، فإننا نسميها كائنات أو مثيلات للفئات. وبالتالي، فإن الفئة هي قالب تُبنى منه الكائنات. يمكن أن تكون عناصر أو حقول الفئة عبارة عن بيانات (سمات)، أو أساليب (دوال)، أو خصائص. الخصائص هي أساليب خاصة تُستخدم لمعرفة أو تحديد قيمة سمات الكائن. ويمكن أن تكون هذه الحقول مصحوبة بإحدى الكلمات الرئيسية الثلاث التالية:
لا يمكن الوصول إلى الحقل الخاص (private) إلا من خلال الطرق الداخلية للفئة فقط | |
يمكن الوصول إلى الحقل العام (public) من خلال أي دالة، سواء كانت مُعرَّفة داخل الفئة أم لا | |
لا يمكن الوصول إلى الحقل المحمي (protected) إلا من خلال الطرق الداخلية للفئة أو كائن مشتق منها (انظر لاحقًا مفهوم التوريث). |
بشكل عام، تُعلن بيانات الفئة على أنها خاصة، بينما تُعلن طرقها وخصائصها على أنها عامة. وهذا يعني أن مستخدم الكائن (المبرمج):
- لن يتمكن من الوصول مباشرةً إلى البيانات الخاصة للكائن
- سيتمكن من استخدام الطرق العامة للكائن، ولا سيما تلك التي تتيح الوصول إلى بياناته الخاصة.
صيغة إعلان الفئة هي كما يلي:
public class classe
private donnée ou méthode ou propriété privée
public donnée ou méthode ou propriété publique
protected donnée ou méthode ou propriété protégée
end class
ترتيب الإعلان عن السمات private و protected و public غير محدد.
3.1.3. تقوم الطريقة بتهيئة
لنعد إلى فئتنا [personne] المُعلنة على النحو التالي:
Public Class personne
' السمات
Private prenom As String
Private nom As String
Private age As Integer
' طريقة
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' طريقة
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
ما هو دور الطريقة initialise؟ نظرًا لأن nom و prenom و age هي بيانات خاصة بالفئة personne، فإن التعليمات التالية:
غير قانونية. يتعين علينا تهيئة كائن من النوع personne عبر طريقة عامة. وهذه هي وظيفة الطريقة initialise. سنكتب:
الكتابة p1.initialise صحيحة لأن initialise متاحة للجميع.
3.1.4. المشغل new
تسلسل الأوامر
غير صحيحة. الأمر
تُعلن p1 كمرجع إلى كائن من النوع personne. هذا الكائن غير موجود بعد، وبالتالي فإن p1 لم يتم تهيئته. وهذا يشبه كتابة:
حيث نشير صراحةً باستخدام الكلمة المفتاحية nothing إلى أن المتغير p1 لا يشير بعد إلى أي كائن. وعندما نكتب بعد ذلك
فإننا نستدعي الطريقة initialise للكائن الذي يشير إليه p1. لكن هذا الكائن لا يوجد بعد، وسيُبلغ المُجمِّع عن الخطأ. لكي يشير p1 إلى كائن، يجب كتابة:
يؤدي ذلك إلى إنشاء كائن من النوع personne لم يتم تهيئته بعد: ستكون قيمة السمتين nom و prenom — وهما إشارات إلى كائنات من النوع String — هي nothing، وستكون قيمة age هي 0. وبالتالي، هناك تهيئة افتراضية. والآن بعد أن أصبح p1 يشير إلى كائن، فإن تعليمة تهيئة هذا الكائن
صالحًا.
3.1.5. الكلمة الرئيسية Me
لنلقِ نظرة على كود الطريقة initialise:
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
تعني التعليمات Me.prenom=P أن السمة prenom للكائن الحالي (Me) تتخذ القيمة P. تشير الكلمة الرئيسية Me إلى الكائن الحالي: وهو الكائن الذي توجد فيه الطريقة التي يتم تنفيذها. كيف نعرف ذلك؟ لنلقِ نظرة على كيفية تهيئة الكائن المشار إليه بواسطة p1 في البرنامج المستدعي:
يتم استدعاء الطريقة initialise الخاصة بالكائن p1. وعندما يتم الإشارة إلى الكائن Me في هذه الطريقة، فإننا نشير في الواقع إلى الكائن p1. كان من الممكن أيضًا كتابة الطريقة initialise على النحو التالي:
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
prenom = P
nom = N
Me.age = age
End Sub
عندما تشير إحدى طرق الكائن إلى سمة A لهذا الكائن، فإن الكتابة Me.A تكون ضمنية. يجب استخدامها صراحةً عند وجود تعارض في المعرفات. وهذا هو الحال في الأمر التالي:
Me.age=age;
حيث يشير age إلى سمة للكائن الحالي وكذلك إلى المعلمة age التي تستقبلها الطريقة. لذلك، يجب إزالة الغموض عن طريق تسمية السمة age بـ Me.age.
3.1.6. برنامج اختبار
فيما يلي برنامج اختبار قصير:
Public Class personne
' السمات
Private prenom As String
Private nom As String
Private age As Integer
' طريقة
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' طريقة
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
والنتائج التي تم الحصول عليها:
dos>vbc personne1.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
3.1.7. استخدام ملف الفئات المُجمَّعة (assembly)
تجدر الإشارة إلى أنه في المثال السابق، يوجد فئتان في برنامج الاختبار الخاص بنا: الفئتان personne و test1. وهناك طريقة أخرى للقيام بذلك:
-
يتم ترجمة فئة «personne» إلى ملف خاص يُسمى «تجميع» (assembly). هذا الملف له امتداد .dll
-
نقوم بتجميع الفئة test1 بالإشارة إلى التجميع الذي يحتوي على فئة personne.
يصبح الملفان المصدريان كما يلي:
test.vb | |
personne2.vb | |
يتم تجميع الفئة personne بواسطة الأمر التالي:
dos>vbc /t:library personne2.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
24/02/2004 16:50 509 personne2.vb
24/02/2004 16:49 143 test.vb
24/02/2004 16:50 3 584 personne2.dll
أنتجت عملية التجميع ملفًا باسم personne2.dll. خيار التجميع /t:library هو الذي يشير إلى إنتاج ملف "تجميع". والآن، لنقم بتجميع الملف test.vb:
dos>vbc /r:personne2.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
24/02/2004 16:50 509 personne2.vb
24/02/2004 16:49 143 test.vb
24/02/2004 16:50 3 584 personne2.dll
24/02/2004 16:51 3 072 test.exe
خيار الترجمة /r:personne2.dll يُعلم المُترجم بأنه سيجد فئات معينة في الملف personne2.dll. وعندما يجد في ملف المصدر test.vb إشارة إلى الفئة personne، وهي فئة غير مُعلنة في ملف المصدر test.vb، فسيبحث عن الفئة personne في ملفات .dll المشار إليها بواسطة الخيار /r. وسيجد هنا الفئة personne في التجميع personne2.dll. كان من الممكن إدراج فئات أخرى في هذا التجميع. لاستخدام عدة ملفات من الفئات المُجمَّعة أثناء التجميع، نكتب:
يؤدي تشغيل البرنامج test1.exe إلى النتائج التالية:
3.1.8. طريقة أخرى تقوم بالتهيئة
لنأخذ الفئة personne مرة أخرى ونضيف إليها الطريقة التالية:
' طريقة
Public Sub initialise(ByVal P As personne)
prenom = P.prenom
nom = P.nom
Me.age = P.age
End Sub
لدينا الآن طريقتان تحملان الاسم initialise: وهذا أمر مقبول طالما أنهما تقبلان معلمات مختلفة. وهذا هو الحال هنا. أصبح المعلمة الآن مرجعًا P إلى شخص. ثم تُعزى سمات الشخص P إلى الكائن الحالي (Me). تجدر الإشارة إلى أن الطريقة initialise تتمتع بوصول مباشر إلى سمات الكائن P على الرغم من أن هذه السمات من النوع private. وهذا صحيح دائمًا: الكائن O1 من الفئة C يتمتع دائمًا بإمكانية الوصول إلى سمات الكائنات من نفس الفئة C. فيما يلي اختبار للفئة الجديدة personne، والتي تم تجميعها في personne.dll كما تم شرحه سابقًا:
' خيارات
Option Explicit On
Option Strict On
' مساحات الأسماء
Imports System
Module test1
Sub Main()
Dim p1 As New personne
p1.initialise("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identifie()
Dim p2 As New personne
p2.initialise(p1)
Console.Out.Write("p2=")
p2.identifie()
End Sub
End Module
وإليكم نتائجها:
3.1.9. منشئات فئة «person»
المنشئ هو إجراء يحمل اسم New ويتم استدعاؤه عند إنشاء الكائن. ويُستخدم عادةً لتهيئة الكائن. إذا كانت الفئة تحتوي على منشئ يقبل n معلمات argi، فيمكن إعلان وتهيئة كائن من هذه الفئة بالصيغة التالية:
dim objet as classe =new classe(arg1,arg2, ... argn)
أو
dim objet as classe
…
objet=new classe(arg1,arg2, ... argn)
عندما تحتوي فئة ما على منشئ واحد أو أكثر، يجب استخدام أحد هذه المنشئات لإنشاء كائن من هذه الفئة. وإذا كانت الفئة C لا تحتوي على أي منشئ، فإنها تمتلك منشئًا افتراضيًّا وهو المنشئ بدون معلمات: public New(). وعندئذٍ يتم تهيئة سمات الكائن بقيم افتراضية. وهذا ما حدث في البرامج السابقة، حيث كتبنا:
لنقم بإنشاء منشئين لفئتنا personne:
' خيارات
Option Explicit On
Option Strict On
' مساحات الأسماء
Imports System
' فئة الشخص
Public Class personne
' السمات
Private prenom As String
Private nom As String
Private age As Integer
' المنشئات
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
initialise(P, N, age)
End Sub
Public Sub New(ByVal P As personne)
initialise(P)
End Sub
' طرق تهيئة الكائن
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
Public Sub initialise(ByVal P As personne)
prenom = P.prenom
nom = P.nom
Me.age = P.age
End Sub
' طريقة
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
يكتفي منشئانا باستدعاء الطرق initialise المقابلة. نذكر أنه عندما نجد في منشئ، على سبيل المثال، الترميز initialise(P)، فإن المُجمِّع يُترجمها إلى Me.initialise(P). وبالتالي، يتم استدعاء الطريقة initialise داخل المنشئ للعمل على الكائن المشار إليه بواسطة Me، أي الكائن الحالي، الذي يجري إنشاؤه في الوقت الحالي. فيما يلي برنامج اختبار قصير:
' خيارات
Option Explicit On
Option Strict On
' مساحات الأسماء
Imports System
' صفحة الاختبار
Module test
Sub Main()
Dim p1 As New personne("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identifie()
Dim p2 As New personne(p1)
Console.Out.Write("p2=")
p2.identifie()
End Sub
End Module
والنتائج التي تم الحصول عليها:
3.1.10. مراجع الكائنات
ما زلنا نستخدم نفس الفئة personne. يصبح برنامج الاختبار كما يلي:
' خيارات
Option Explicit On
Option Strict On
' مساحات الأسماء
Imports System
' صفحة اختبار
Module test
Sub Main()
' p1
Dim p1 As New personne("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identifie()
' p2 تشير إلى نفس الكائن الذي تشير إليه p1
Dim p2 As personne = p1
Console.Out.Write("p2=")
p2.identifie()
' p3 يشير إلى كائن سيكون نسخة من الكائن الذي يشير إليه p1
Dim p3 As New personne(p1)
Console.Out.Write("p3=")
p3.identifie()
' يتم تغيير حالة الكائن الذي يشير إليه p1
p1.initialise("Micheline", "Benoît", 67)
Console.Out.Write("p1=")
p1.identifie()
' بما أن p2=p1، فلا بد أن الكائن الذي يشير إليه p2 قد تغيرت حالته
Console.Out.Write("p2=")
p2.identifie()
' بما أن p3 لا يشير إلى نفس الكائن الذي يشير إليه p1، فلا بد أن الكائن الذي يشير إليه p3 لم يتغير
Console.Out.Write("p3=")
p3.identifie()
End Sub
End Module
النتائج التي تم الحصول عليها هي كما يلي:
p1=Jean,Dupont,30
p2=Jean,Dupont,30
p3=Jean,Dupont,30
p1=Micheline,Benoît,67
p2=Micheline,Benoît,67
p3=Jean,Dupont,30
عند تعريف المتغير p1 بواسطة
فإن p1 يشير إلى الكائن personne("Jean","Dupont",30) ولكنه ليس الكائن نفسه. في لغة C، يمكن القول إنه مؤشر، c.a.d، وهو عنوان الكائن الذي تم إنشاؤه. إذا كتبنا بعد ذلك:
فليس الكائن personne("Jean","Dupont",30) هو الذي يتم تعديله، بل الإشارة p1 هي التي تتغير قيمتها. وسيُفقد الكائن personne("Jean","Dupont",30) إذا لم تتم الإشارة إليه بواسطة أي متغير آخر.
عند كتابة:
نقوم بتهيئة المؤشر p2: فهو «يشير» إلى نفس الكائن (يُشير إلى نفس الكائن) الذي يشير إليه المؤشر p1. وبالتالي، إذا قمنا بتعديل الكائن الذي «يشير» إليه (أو المشار إليه) المؤشر p1، فإننا نعدل الكائن المشار إليه بواسطة المؤشر p2.
عند كتابة:
يتم إنشاء كائن جديد، وهو نسخة من الكائن المشار إليه بواسطة p1. وسيتم الإشارة إلى هذا الكائن الجديد بواسطة p3. إذا قمنا بتعديل الكائن «المُشار إليه» (أو المشار إليه) بواسطة p1، فلن يؤثر ذلك بأي شكل من الأشكال على الكائن المشار إليه بواسطة p3. وهذا ما توضحه النتائج التي تم الحصول عليها.
3.1.11. الكائنات المؤقتة
في تعبير ما، يمكننا استدعاء منشئ كائن بشكل صريح: يتم إنشاء هذا الكائن، لكن لا يمكننا الوصول إليه (لتعديله على سبيل المثال). يتم إنشاء هذا الكائن المؤقت لتلبية احتياجات تقييم التعبير ثم يتم التخلي عنه. سيتم استرداد مساحة الذاكرة التي كان يشغلها تلقائيًا لاحقًا بواسطة برنامج يُسمى «مجمع القمامة»، وتتمثل مهمته في استرداد مساحة الذاكرة التي تشغلها الكائنات التي لم تعد مرجعية لها أي بيانات في البرنامج. لننظر إلى برنامج الاختبار الجديد التالي:
' خيارات
Option Strict On
Option Explicit On
' مساحات الأسماء
Imports System
' صفحة اختبار
Module test
Sub Main()
Dim p As New personne(New personne("Jean", "Dupont", 30))
p.identifie()
End Sub
End Module
ولنعدّل منشئات الفئة personne بحيث تعرض رسالة:
' المنشئات
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
Console.Out.WriteLine("Constructeur personne(String, String, integer)")
initialise(P, N, age)
End Sub
Public Sub New(ByVal P As personne)
Console.Out.WriteLine("Constructeur personne(personne)")
initialise(P)
End Sub
ونحصل على النتائج التالية:
dos>test
Constructeur personne(String, String, integer)
Constructeur personne(personne)
Jean,Dupont,30
مما يوضح البناء المتتالي للكائنين المؤقتين.
3.1.12. طرق قراءة وكتابة السمات الخاصة
نضيف إلى الفئة personne الطرق اللازمة لقراءة أو تعديل حالة سمات الكائنات:
Imports System
Public Class personne
' السمات
Private prenom As [String]
Private nom As [String]
Private age As Integer
' المنشئات
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
Public Sub New(ByVal P As personne)
Me.prenom = P.prenom
Me.nom = P.nom
Me.age = P.age
End Sub
' المعرفات
Public Sub identifie()
Console.Out.WriteLine((prenom + "," + nom + "," + age))
End Sub
' أدوات الوصول
Public Function getPrenom() As [String]
Return prenom
End Function
Public Function getNom() As [String]
Return nom
End Function
Public Function getAge() As Integer
Return age
End Function
' المعدِّلات
Public Sub setPrenom(ByVal P As [String])
Me.prenom = P
End Sub
Public Sub setNom(ByVal N As [String])
Me.nom = N
End Sub
Public Sub setAge(ByVal age As Integer)
Me.age = age
End Sub
End Class
نختبر الفئة الجديدة باستخدام البرنامج التالي:
' خيارات
Option Strict On
Option Explicit On
' مساحات الأسماء
Imports System
' صفحة اختبار
Public Module test
Sub Main()
Dim P As New personne("Jean", "Michelin", 34)
Console.Out.WriteLine(("P=(" & P.getPrenom() & "," & P.getNom() & "," & P.getAge() & ")"))
P.setAge(56)
Console.Out.WriteLine(("P=(" & P.getPrenom() & "," & P.getNom() & "," & P.getAge() & ")"))
End Sub
End Module
ونحصل على النتائج التالية:
3.1.13. الخصائص
هناك طريقة أخرى للوصول إلى سمات الفئة، وهي إنشاء خصائص. تتيح لنا هذه الخصائص التعامل مع السمات الخاصة كما لو كانت عامة. لننظر إلى الفئة personne التالية، حيث تم استبدال أدوات الوصول والتعديل السابقة بخصائص للقراءة والكتابة:
' خيارات
Option Explicit On
Option Strict On
' مساحات الأسماء
Imports System
' فئة الشخص
Public Class personne
' السمات
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' المنشئات
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
Me._prenom = P
Me._nom = N
Me._age = age
End Sub
Public Sub New(ByVal P As personne)
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
End Sub
' المعرفات
Public Sub identifie()
Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
End Sub
' الخصائص
Public Property prenom() As String
Get
Return _prenom
End Get
Set(ByVal Value As String)
_prenom = Value
End Set
End Property
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = Value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
' هل العمر صالح؟
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
End Class
تسمح الخاصية (Property) بقراءة (get) أو تعيين (set) قيمة السمة. في مثالنا، أضفنا البادئة _ إلى أسماء السمات بحيث تحمل الخصائص أسماء السمات الأولية. في الواقع، لا يمكن أن تحمل الخاصية نفس اسم السمة التي تديرها، لأن ذلك سيؤدي إلى تعارض في الأسماء داخل الفئة. لذلك أطلقنا على سماتنا أسماء _prenom و _nom و _age وقمنا بتعديل منشئات الكائنات والأساليب وفقًا لذلك. ثم أنشأنا ثلاث خصائص هي nom و prenom و age. يتم إعلان الخاصية على النحو التالي:
Public Property nom() As Type
Get
...
End Get
Set(ByVal Value As Type)
...
End Set
End Property
حيث يجب أن يكون Type هو نوع السمة التي تديرها الخاصية. ويمكن أن تحتوي على طريقتين تسميان get و set. عادةً ما تكون الطريقة get مسؤولة عن إرجاع قيمة السمة التي تديرها (ويمكنها إرجاع شيء آخر، فلا يوجد ما يمنع ذلك). أما الطريقة set فتستقبل معلمة تُسمى value وتقوم عادةً بتعيينها للسمة التي تديرها. ويمكنها استغلال هذه الفرصة للتحقق من صحة القيمة المستلمة، وربما إثارة استثناء إذا تبين أن القيمة غير صالحة. وهذا ما يتم هنا بالنسبة للعمر.
كيف يتم استدعاء هاتين الطريقتين get و set؟ لننظر إلى برنامج الاختبار التالي:
' خيارات
Option Explicit On
Option Strict On
' مساحات الأسماء
Imports System
' صفحة الاختبار
Module test
Sub Main()
Dim P As New personne("Jean", "Michelin", 34)
Console.Out.WriteLine(("P=(" & P.prenom & "," & P.nom & "," & P.age & ")"))
P.age = 56
Console.Out.WriteLine(("P=(" & P.prenom & "," & P.nom & "," & P.age & ")"))
Try
P.age = -4
Catch ex As Exception
Console.Error.WriteLine(ex.Message)
End Try
End Sub
End Module
في الأمر
نحاول الحصول على قيم الخصائص prenom و nom و age الخاصة بالشخص P. يتم عندئذٍ استدعاء طريقة get الخاصة بهذه الخصائص، والتي تُرجع قيمة السمة التي تديرها.
في الأمر
نريد تعيين قيمة الخاصية age. وعندها يتم استدعاء طريقة set لهذه الخاصية. وستتلقى القيمة 56 في معلمتها value.
يُطلق على الخاصية P لفئة C التي لا تُعرِّف سوى طريقة get اسم «للقراءة فقط». وإذا كان c كائنًا من فئة C، فسيرفض المُجمِّع العملية c.P=قيمة.
يؤدي تنفيذ برنامج الاختبار السابق إلى النتائج التالية:
وبالتالي، تتيح لنا الخصائص معالجة السمات الخاصة كما لو كانت عامة.
3.1.14. طرق الفئة وسماتها
لنفترض أننا نريد حساب عدد الكائنات [personne] التي تم إنشاؤها في أحد التطبيقات. يمكننا إدارة عداد بأنفسنا، لكننا قد ننسى الكائنات المؤقتة التي يتم إنشاؤها هنا أو هناك. يبدو أنه من الأكثر أمانًا تضمين تعليمة تزيد العداد في منشئات الفئة [personne]. تكمن المشكلة في تمرير مرجع لهذا العداد حتى تتمكن منشئة الكائنات من زيادته: لذا يجب تمرير معلمة جديدة إليها. يمكن أيضًا تضمين العداد في تعريف الفئة. وبما أنه سمة تابعة للفئة نفسها وليس لكائن معين من هذه الفئة، يتم إعلانه بطريقة مختلفة باستخدام الكلمة الرئيسية Shared:
للإشارة إليه، نكتب personne._nbPersonnes لإظهار أنه سمة للفئة personne نفسها. هنا، أنشأنا سمة خاصة لا يمكن الوصول إليها مباشرةً من خارج الفئة. لذلك، نقوم بإنشاء خاصية عامة لإتاحة الوصول إلى سمة الفئة nbPersonnes. ولجعل قيمة nbPersonnes، لا تحتاج الطريقة get الخاصة بهذه الخاصية إلى كائن personne معين: ففي الواقع، _nbPersonnes ليس سمة لكائن معين، بل هو سمة لفئة بأكملها. ولذلك نحتاج إلى خاصية مُعلنة أيضًا باسم Shared:
والتي سيتم استدعاؤها من الخارج باستخدام الصيغة personne.nbPersonnes. يتم تعريف الخاصية على أنها للقراءة فقط (ReadOnly) لأنها لا توفر طريقة تعيين. فيما يلي مثال على ذلك. تصبح الفئة personne كما يلي:
Option Explicit On
Option Strict On
' مساحات الأسماء
Imports System
' فئة
Public Class personne
' سمات الفئة
Private Shared _nbPersonnes As Long = 0
' سمات المثيل
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' منشئات
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
' شخص آخر
_nbPersonnes += 1
Me._prenom = P
Me._nom = N
Me._age = age
End Sub
Public Sub New(ByVal P As personne)
' شخص آخر
_nbPersonnes += 1
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
End Sub
' معرف
Public Sub identifie()
Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
End Sub
' خاصية الفئة
Public Shared ReadOnly Property nbPersonnes() As Long
Get
Return _nbPersonnes
End Get
End Property
' خصائص المثيل
Public Property prenom() As String
Get
Return _prenom
End Get
Set(ByVal Value As String)
_prenom = Value
End Set
End Property
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = Value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
' هل العمر صالح؟
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
End Class
مع البرنامج التالي:
' خيارات
Option Explicit On
Option Strict On
' مساحات الأسماء
Imports System
' صفحة الاختبار
Module test
Sub Main()
Dim p1 As New personne("Jean", "Dupont", 30)
Dim p2 As New personne(p1)
Console.Out.WriteLine(("Nombre de personnes créées : " & personne.nbPersonnes))
End Sub
End Module
نحصل على النتائج التالية:
3.1.15. تمرير كائن إلى دالة
سبق أن ذكرنا أن VB.NET تقوم افتراضيًا بتمرير المعلمات الفعلية للدالة بالقيمة: حيث يتم نسخ قيم المعلمات الفعلية إلى المعلمات الشكلية. وفي حالة الكائن، يجب ألا ننخدع بالاستخدام الخاطئ للغة الذي يحدث بشكل منهجي عند الحديث عن «الكائن» بدلاً من «مرجع الكائن». لا يتم التعامل مع الكائن إلا من خلال مرجع (مؤشر) إليه. وبالتالي، فإن ما يتم تمريره إلى الدالة ليس الكائن نفسه، بل مرجعًا إلى هذا الكائن. لذا، فإن قيمة المرجع — وليس قيمة الكائن نفسه — هي التي يتم نسخها في المعلمة الشكلية: ولا يتم إنشاء كائن جديد. إذا تم تمرير مرجع الكائن R1 إلى دالة، فسيتم نسخه في المعلمة الشكلية المقابلة R2. وبالتالي، فإن المرجعين R2 و R1 يشيران إلى نفس الكائن. وإذا قامت الدالة بتعديل الكائن الذي يشير إليه R2، فمن الواضح أنها ستعدل الكائن الذي يشير إليه R1 أيضًا، نظرًا لأنهما متطابقان.
![]() |
وهذا ما يوضحه المثال التالي:
' خيارات
Option Explicit On
Option Strict On
' مساحات الأسماء
Imports System
' صفحة اختبار
Module test
Sub Main()
' شخص p1
Dim p1 As New personne("Jean", "Dupont", 30)
' عرض p1
Console.Out.Write("Paramètre effectif avant modification : ")
p1.identifie()
' تعديل p1
modifie(p1)
' عرض p1
Console.Out.Write("Paramètre effectif après modification : ")
p1.identifie()
End Sub
Sub modifie(ByVal P As personne)
' عرض الشخص P
Console.Out.Write("Paramètre formel avant modification : ")
P.identifie()
' تعديل P
P.prenom = "Sylvie"
P.nom = "Vartan"
P.age = 52
' عرض P
Console.Out.Write("Paramètre formel après modification : ")
P.identifie()
End Sub
End Module
النتائج التي تم الحصول عليها هي كما يلي:
Paramètre effectif avant modification : Jean,Dupont,30
Paramètre formel avant modification : Jean,Dupont,30
Paramètre formel après modification : Sylvie,Vartan,52
Paramètre effectif après modification : Sylvie,Vartan,52
نلاحظ أنه لم يتم إنشاء سوى كائن واحد: وهو الشخص p1 الخاص بالإجراء Main، وأن الكائن قد تم تعديله بالفعل بواسطة الدالة modifie.
3.1.16. مصفوفة من الأشخاص
الكائن هو بيانات مثل أي بيانات أخرى، وبهذا الصفة يمكن تجميع عدة كائنات في مصفوفة:
' خيارات
Option Explicit On
Option Strict On
' مساحات الأسماء
Imports System
' صفحة اختبار
Module test
Sub Main()
' جدول الأشخاص
Dim amis(2) As personne
amis(0) = New personne("Jean", "Dupont", 30)
amis(1) = New personne("Sylvie", "Vartan", 52)
amis(2) = New personne("Neil", "Armstrong", 66)
' العرض
Console.Out.WriteLine("----------------")
Dim i As Integer
For i = 0 To amis.Length - 1
amis(i).identifie()
Next i
End Sub
End Module
تُنشئ العبارة Dim amis(2) As personne مصفوفة مكونة من 3 عناصر من النوع personne. يتم تهيئة هذه العناصر الثلاثة هنا بالقيم nothing و c.a.d، وهي لا تشير إلى أي كائن. مرة أخرى، وبسبب سوء استخدام المصطلحات، نُطلق مصطلح «مصفوفة كائنات» في حين أنها ليست سوى مصفوفة من مراجع الكائنات. إن إنشاء مصفوفة الكائنات، التي تُعد كائنًا بحد ذاتها، لا يؤدي إلى إنشاء أي كائن من نوع عناصرها: بل يجب القيام بذلك لاحقًا. ونحصل على النتائج التالية:
3.2. الوراثة من خلال المثال
3.2.1. مقدمة
نتناول هنا مفهوم التوريث. الهدف من التوريث هو "تخصيص" فئة موجودة لتلبي احتياجاتنا. لنفترض أننا نريد إنشاء فئة enseignant: المعلم هو شخص معين. ولديه سمات لا يمتلكها أي شخص آخر: المادة التي يدرسها على سبيل المثال. لكنه يمتلك أيضًا سمات أي شخص: الاسم الأول، واسم العائلة، والعمر. لذا فإن المعلم ينتمي تمامًا إلى الفئة personne ولكنه يمتلك سمات إضافية. بدلاً من إنشاء فئة enseignant من الصفر، نفضل الاستفادة من مكونات فئة personne وتكييفها مع السمات الخاصة بالمعلمين. وهذا ما يتيحه لنا مفهوم التوريث. وللتعبير عن أن الفئة enseignant ترث خصائص الفئة personne، نكتب:
Public Class enseignant
Inherits personne
يُلاحظ هنا الصيغة الخاصة المكتوبة على سطرين. تُسمى الفئة personne بالفئة الأصلية (أو الأم)، بينما تُسمى الفئة enseignant بالفئة المشتقة (أو الابنة). يتمتع الكائن enseignant بجميع خصائص الكائن personne: فهو يمتلك نفس السمات ونفس الأساليب. ولا يتم تكرار هذه السمات والأساليب الخاصة بالفئة الأم في تعريف الفئة الفرعية: بل يكتفى بالإشارة إلى السمات والأساليب التي أضافتها الفئة الفرعية. نفترض أن الفئة personne مُعرَّفة على النحو التالي:
' خيارات
Option Strict On
Option Explicit On
' مساحات الأسماء
Imports System
' فئة الشخص
Public Class personne
' سمات الفئة
Private Shared _nbPersonnes As Long = 0
' سمات المثيل
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' المنشئات
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
' شخص آخر
_nbPersonnes += 1
' التنشئة
Me._prenom = P
Me._nom = N
Me._age = age
' المتابعة
Console.Out.WriteLine("Construction personne(string, string, int)")
End Sub
Public Sub New(ByVal P As personne)
' شخص إضافي
_nbPersonnes += 1
' البناء
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
' المتابعة
Console.Out.WriteLine("Construction personne(string, string, int)")
End Sub
' خاصية الفئة
Public Shared ReadOnly Property nbPersonnes() As Long
Get
Return _nbPersonnes
End Get
End Property
' خصائص المثيل
Public Property prenom() As String
Get
Return _prenom
End Get
Set(ByVal Value As String)
_prenom = Value
End Set
End Property
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = Value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
' هل العمر صالح؟
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
Public ReadOnly Property identite() As String
Get
Return "personne(" & _prenom & "," & _nom & "," & age & ")"
End Get
End Property
End Class
تم استبدال الطريقة identifie بالخاصية identité التي تكون للقراءة فقط وتحدد هوية الشخص. نقوم بإنشاء فئة enseignant ترث من الفئة personne:
' خيارات
Option Strict On
Option Explicit On
' مساحات الأسماء
Imports System
Public Class enseignant
Inherits personne
' السمات
Private _section As Integer
' المنشئ
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
MyBase.New(P, N, age)
Me._section = section
' التتبع
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
' خاصية القسم
Public Property section() As Integer
Get
Return _section
End Get
Set(ByVal Value As Integer)
_section = Value
End Set
End Property
End Class
تضيف الفئة enseignant إلى أساليب وسمات الفئة personne:
- سمة section وهي رقم القسم الذي ينتمي إليه المعلم ضمن هيئة التدريس (قسم واحد لكل تخصص بشكل عام)
- منشئ جديد يسمح بتهيئة جميع سمات المعلم
الإعلان
Public Class enseignant
Inherits personne
يشير إلى أن الفئة enseignant مشتقة من الفئة personne.
3.2.2. إنشاء كائن «مدرس»
منشئ الفئة enseignant هو كما يلي:
' المنشئ
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
MyBase.New(P, N, age)
Me._section = section
' المتابعة
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
البيان
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
يُذكر أن المُصنِّع يتلقى أربعة معلمات هي P، N، age، section. ويجب عليها تمرير ثلاثة منها، وهي (P,N,age)، إلى فئتها الأساسية، وهي في هذه الحالة الفئة personne. ومن المعروف أن هذه الفئة تحتوي على منشئ person(string, string, int) الذي سيسمح بإنشاء شخص باستخدام المعلمات التي تم تمريرها (P,N,age). تقوم الفئة [enseignant] بتمرير المعلمات (P, N, age) إلى فئتها الأساسية بالطريقة التالية:
MyBase.New(P, N, age)
بمجرد انتهاء إنشاء الفئة الأساسية، يستمر إنشاء الكائن enseignant بتنفيذ نص منشئ الكائن:
Me._section = section
باختصار، يقوم منشئ الفئة المشتقة:
- يمرر إلى فئته الأساسية المعلمات التي تحتاجها لإنشائها
- يستخدم المعلمات الأخرى لتهيئة السمات الخاصة به
كان من الممكن كتابة ما يلي بدلاً من ذلك:
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
Me._prenom = P
Me._nom = N
Me._age = age
Me._section = section
' المتابعة
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
هذا مستحيل. فقد أعلنت الفئة personne أن حقولها الثلاثة _prenom و _nom و _age هي حقول خاصة (private). ولا يمكن الوصول المباشر إلى هذه الحقول إلا من خلال كائنات من نفس الفئة. أما جميع الكائنات الأخرى، بما في ذلك الكائنات الفرعية كما في هذه الحالة، فيجب أن تمر عبر الطرق العامة للوصول إليها. وكان الأمر سيختلف لو أن الفئة personne قد أعلنت الحقول الثلاثة محمية (protected)؛ ففي هذه الحالة كانت ستسمح للفئات المشتقة بالوصول المباشر إلى الحقول الثلاثة. في مثالنا، كان استخدام مُنشئ الفئة الأم هو الحل الصحيح، وهذه هي الطريقة المعتادة: عند إنشاء كائن فرعي، نستدعي أولاً مُنشئ الكائن الأم، ثم نكمل عمليات التهيئة الخاصة بالكائن الفرعي (section في مثالنا).
لنقوم بتجميع الفئتين personne و enseignant في تجميعات:
dos>vbc /t:library personne.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>vbc /r:personne.dll /t:library enseignant.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
25/02/2004 10:08 1 828 personne.vb
25/02/2004 10:11 675 enseignant.vb
25/02/2004 10:12 223 test.vb
25/02/2004 10:16 4 096 personne.dll
25/02/2004 10:16 3 584 enseignant.dll
يُلاحظ أنه لتجميع الفئة الفرعية enseignant، كان لا بد من الإشارة إلى الملف personne.dll الذي يحتوي على الفئة personne. دعونا نجرب برنامج اختبار أولي:
' خيارات
Option Strict On
Option Explicit On
' مساحات الأسماء
Imports System
Module test
Sub Main()
Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite)
End Sub
End Module
يقتصر هذا البرنامج على إنشاء كائن enseignant (new) وتحديد هويته. لا تحتوي الفئة enseignant على طريقة identité، لكن فئتها الأم تحتوي على طريقة من هذا النوع، وهي طريقة عامة: وبموجب الوراثة، تصبح هذه الطريقة طريقة عامة للفئة enseignant. النتائج التي تم الحصول عليها هي كما يلي:
dos>vbc /r:personne.dll /r:enseignant.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>test
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(Jean,Dupont,30)
نلاحظ أن:
- تم إنشاء الكائن personne قبل الكائن enseignant
- الهوية التي تم الحصول عليها هي هوية الكائن personne
3.2.3. تحميل طريقة أو خاصية
في المثال السابق، حصلنا على هوية الجزء personne الخاص بالمدرس، لكن هناك بعض المعلومات الخاصة بالفئة enseignant (القسم) غير متوفرة. لذلك، يتعين علينا كتابة خاصية تسمح بتحديد هوية المدرس:
' خيارات
Option Strict On
Option Explicit On
' مساحات الأسماء
Imports System
Public Class enseignant
Inherits personne
' السمات
Private _section As Integer
' المنشئ
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
MyBase.New(P, N, age)
Me._section = section
' التتبع
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
' خاصية القسم
Public Property section() As Integer
Get
Return _section
End Get
Set(ByVal Value As Integer)
_section = Value
End Set
End Property
' تحميل خاصية الهوية
Public Shadows ReadOnly Property identite() As String
Get
Return "enseignant(" & MyBase.identite & "," & _section & ")"
End Get
End Property
End Class
تعتمد الطريقة identite الخاصة بالفئة enseignant على الطريقة identite الخاصة بفئتها الأم (MyBase.identite) لعرض الجزء الخاص بها «personne»، ثم تُستكمل بالحقل _section الخاص بالفئة enseignant. تجدر الإشارة إلى إعلان الخاصية identite:
Public Shadows ReadOnly Property identite() As String
التي تشير إلى أن الخاصية «تخفي» الطريقة التي تحمل الاسم نفسه والتي قد تكون موجودة في الفئة الأم. لنفترض وجود كائن enseignant يُسمى E. يحتوي هذا الكائن في داخله على كائن personne:
![]() |
يتم تعريف الخاصية identity في كل من الفئة enseignant وفئتها الأم personne. في الفئة الفرعية enseignant، يجب أن تسبق الخاصية identite الكلمة المفتاحية shadows للإشارة إلى إعادة تعريف خاصية جديدة identite للفئة enseignant.
Public Shadows ReadOnly Property identite() As String
تحتوي الفئة enseignant الآن على خاصيتين identite:
- الخاصية الموروثة من الفئة الأم personne
- والأخرى الخاصة بها
إذا كان E كائنًا من نوع enseignant، فإن E.identite تشير إلى الطريقة identite الخاصة بالفئة enseignant. يُقال إن الخاصية identite للفئة الأم «أُعيد تعريفها» بواسطة الخاصية identite للفئة الفرعية. بشكل عام، إذا كان O كائنًا وM طريقة، لتنفيذ الطريقة O.M، يبحث النظام عن طريقة M بالترتيب التالي:
- في فئة الكائن O
- في فئته الأم إن وجدت
- في الفئة الأم لفئته الأم إن وجدت
- وهكذا...
وبالتالي، يتيح التوريث إعادة تعريف الطرق/الخصائص التي تحمل نفس الاسم في الفئة الأم داخل الفئة الفرعية. وهذا ما يسمح بتكييف الفئة الفرعية وفقًا لاحتياجاتها الخاصة. وبالاقتران مع تعدد الأشكال الذي سنناقشه لاحقًا، فإن إعادة تعريف الطرق/الخصائص يمثل الميزة الرئيسية للتوريث. لنأخذ نفس المثال السابق:
' خيارات
Option Strict On
Option Explicit On
' مساحات الأسماء
Imports System
Module test
Sub Main()
Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite)
End Sub
End Module
والنتائج التي تم الحصول عليها هذه المرة هي التالية:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Jean,Dupont,30),27)
3.2.4. التعدد الشكلي
لنفترض وجود سلسلة من الفئات: C0 C1 C2 … Cn حيث Ci Cj تشير إلى أن الفئة Cj مشتقة من الفئة Ci. وينتج عن ذلك أن الفئة Cj تمتلك جميع خصائص الفئة Ci بالإضافة إلى خصائص أخرى. لنفترض أن هناك كائنات Oi من النوع Ci. من المسموح كتابة:
فبالواقع، وبموجب مبدأ التوريث، تمتلك الفئة Cj جميع خصائص الفئة Ci بالإضافة إلى خصائص أخرى. وبالتالي، فإن الكائن Oj من النوع Cj يحتوي في داخله على كائن من النوع Ci. العملية
تجعل Oi مرجعًا للكائن من النوع Ci الموجود داخل الكائن Oj.
إن قدرة متغير Oi من الفئة Ci على الإشارة ليس فقط إلى كائن من الفئة Ci، بل إلى أي كائن مشتق من الفئة Ci، تُسمى تعدد الأشكال: وهي قدرة المتغير على الإشارة إلى أنواع مختلفة من الكائنات. لنأخذ مثالاً وننظر إلى الدالة التالية المستقلة عن أي فئة:
Sub affiche(ByVal p As personne)
يمكننا أيضًا كتابة
أن
في الحالة الأخيرة، سيتلقى المعلمة الشكلية من النوع personne للدالة affiche قيمة من النوع enseignant. ونظرًا لأن النوع enseignant مشتق من النوع personne، فإن ذلك يعتبر صحيحًا.
3.2.5. إعادة التعريف والتعدد الشكلي
دعونا نكمل الإجراء affiche:
Sub affiche(ByVal p As personne)
' عرض هوية p
Console.Out.WriteLine(p.identite)
End Sub
تُرجع الدالة p.identite سلسلة أحرف تحدد الكائن personne. ماذا يحدث في المثال السابق في حالة الكائن enseignant:
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
لنلقِ نظرة على المثال التالي:
' خيارات
Option Strict On
Option Explicit On
' مساحات الأسماء
Imports System
Module test
Sub Main()
' معلم
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' شخص
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' يعرض
Sub affiche(ByVal p As personne)
' تحديد هوية p
Console.Out.WriteLine(p.identite)
End Sub
End Module
النتائج التي تم الحصول عليها هي كما يلي:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(Lucile,Dumas,56)
Construction personne(string, string, int)
personne(Jean,Dupont,30)
يُظهر التنفيذ أن الأمر p.identite قد نفذ في كل مرة الخاصية identite الخاصة بـ personne، ثم القيمة e الموجودة في enseignant، ثم personne p نفسها. ولم تتوافق مع الكائن الذي تم تمريره فعليًا كمعلمة إلى affiche. كنا نفضل الحصول على الهوية الكاملة لـ enseignant e. ولتحقيق ذلك، كان يجب أن تشير الترميز p.identite إلى الخاصية identite للكائن الذي يشير إليه فعليًا p بدلاً من الخاصية identite الخاصة بجزء "personne" للكائن الذي يشير إليه فعليًا p. يمكن الحصول على هذه النتيجة من خلال تعريف identite كخاصية قابلة للتجاوز (overridable) في الفئة الأساسية personne:
Public Overridable ReadOnly Property identite() As String
Get
Return "personne(" & _prenom & "," & _nom & "," & age & ")"
End Get
End Property
الكلمة الرئيسية overridable تجعل من identite خاصية قابلة لإعادة التعريف أو افتراضية. ويمكن تطبيق هذه الكلمة الرئيسية أيضًا على الطرق. يجب على الفئات الفرعية التي تعيد تعريف خاصية أو طريقة افتراضية أن تستخدم الكلمة المفتاحية overrides بدلاً من shadows لتوصيف خاصيتها/طريقتها المعاد تعريفها. وهكذا، في الفئة enseignant، يتم تعريف الخاصية identite على النحو التالي:
' تعيين خاصية الهوية
Public Overrides ReadOnly Property identite() As String
Get
Return "enseignant(" & MyBase.identite & "," & _section & ")"
End Get
End Property
برنامج الاختبار:
' خيارات
Option Strict On
Option Explicit On
' مساحات الأسماء
Imports System
Module test
Sub Main()
' مدرس
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' شخص
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' عرض
Sub affiche(ByVal p As personne)
' يعرض هوية p
Console.Out.WriteLine(p.identite)
End Sub
End Module
يُنتج عندئذ النتائج التالية:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Lucile,Dumas,56),61)
Construction personne(string, string, int)
personne(Jean,Dupont,30)
هذه المرة، حصلنا بالفعل على الهوية الكاملة للمعلم. لنعد الآن تعريف طريقة بدلاً من خاصية. الفئة object هي الفئة "الأم" لجميع الفئات VB.NET. لذا عندما نكتب:
فإننا نكتب ضمناً:
تُعرِّف الفئة object طريقة افتراضية باسم ToString:

تُرجع الطريقة ToString اسم الفئة التي ينتمي إليها الكائن كما هو موضح في المثال التالي:
' خيارات
Option Strict On
Option Explicit On
' مساحات الأسماء
Imports System
Module test2
Sub Main()
' مدرس
Console.Out.WriteLine(New enseignant("Lucile", "Dumas", 56, 61).ToString())
' شخص
Console.Out.WriteLine(New personne("Jean", "Dupont", 30).ToString())
End Sub
End Module
والنتائج الناتجة هي كما يلي:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant
Construction personne(string, string, int)
personne
يُلاحظ أنه على الرغم من أننا لم نُعد تعريف الطريقة ToString في الفئتين personne و enseignant، إلا أنه يمكن ملاحظة أن الطريقة ToString في الفئة object قادرة مع ذلك على عرض الاسم الفعلي لفئة الكائن. لنُعيد تعريف الطريقة ToString في الفئتين personne و enseignant:
' ToString
Public Overrides Function ToString() As String
' تعيين الخاصية إلى «identite»
Return identite
End Function
التعريف هو نفسه في كلتا الفئتين. لننظر إلى برنامج الاختبار التالي:
' خيارات
Option Strict On
Option Explicit On
' مساحات الأسماء
Imports System
Module test
Sub Main()
' مدرس
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' شخص
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' يعرض
Sub affiche(ByVal p As personne)
' يعرض هوية p
Console.Out.WriteLine(p.identite)
End Sub
End Module
نتائج التنفيذ هي كما يلي:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Lucile,Dumas,56),61)
Construction personne(string, string, int)
personne(Jean,Dupont,30)
3.3. تحديد مُفهرس لفئة
لنأخذ الفئة [ArrayList] المُعرَّفة مسبقًا في منصة .NET. تتيح هذه الفئة تخزين الكائنات في قائمة. وهي تنتمي إلى مساحة الأسماء [System.Collections]. في المثال التالي، نستخدم هذه الفئة لتخزين قائمة بالأشخاص (بالمعنى الواسع للكلمة):
' خيارات
Option Strict On
Option Explicit On
' مساحات الأسماء
Imports System
Imports System.Collections
' فئة listeDePersonnes
Public Class listeDePersonnes
Inherits ArrayList
' لإضافة شخص إلى القائمة
Public Overloads Sub Add(ByVal p As personne)
MyBase.Add(p)
End Sub
' مفهرس
Default Public Shadows Property Item(ByVal i As Integer) As personne
Get
Return CType(MyBase.Item(i), personne)
End Get
Set(ByVal Value As personne)
MyBase.Item(i) = Value
End Set
End Property
' مفهرس آخر
Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
Get
' نبحث عن الشخص الذي اسمه N
Dim i As Integer
For i = 0 To Count - 1
If CType(Me(i), personne).nom = N Then
Return i
End If
Next i
Return -1
End Get
End Property
' toString
Public Overrides Function ToString() As String
' تُرجع (عنصر1، عنصر2، ...، عنصرn)
Dim liste As String = "("
Dim i As Integer
' يتم تصفح المصفوفة الديناميكية
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' العنصر الأخير
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
End Class
دعونا نقدم بعض المعلومات حول بعض خصائص وأساليب الفئة [ArrayList]:
سمة تُحدد عدد عناصر القائمة | |
طريقة تسمح بإضافة كائن إلى القائمة | |
طريقة تُرجع العنصر i من القائمة |
نلاحظ أنه للحصول على العنصر رقم i من القائمة، لم نكتب [liste.Item(i)] بل كتبنا مباشرةً [liste(i)]، وهو ما يبدو في البداية خطأً. ومع ذلك، فإن هذا ممكن لأن الفئة [ArrayList] تُعرِّف خاصية افتراضية [Item] وفقًا لصيغة مشابهة لما يلي:
Default Public Property Item(ByVal i As Integer) As Object
Get
...
End Get
Set(ByVal Value As personne)
...
End Set
End Property
عندما يصادف المُجمِّع الترميز [liste(i)]، فإنه يبحث عما إذا كانت الفئة [ArrayList] قد عرَّفت خاصية بالتوقيع التالي:
Default Public Property Proc(ByVal var As Integer) As Type
وهنا، سيجد الإجراء [Item]. ثم سيقوم بترجمة الترميز [liste(i)] إلى [liste.Item(i)]. يُطلق على الخاصية [Item] اسم «الخاصية المفهرسة افتراضيًا» للفئة [ArrayList]. ويؤدي تنفيذ البرنامج السابق إلى النتائج التالية:
dos>vbc /r:personne.dll /r:enseignant.dll lstpersonnes1.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
11/03/2004 18:26 3 584 enseignant.dll
12/03/2004 15:34 3 584 lstpersonnes1.exe
12/03/2004 13:39 661 lstpersonnes1.vb
11/03/2004 18:26 4 096 personne.dll
dos>lstpersonnes1
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(paul,chenou,31)
personne(nicole,chenou,11)
enseignant(personne(jacques,sileau,33),61)
لننشئ فئة باسم [listeDePersonnes] تكون عبارة عن قائمة بالأشخاص، أي قائمة خاصة يبدو من الطبيعي اشتقاقها من الفئة [ArrayList]:
' خيارات
Option Strict On
Option Explicit On
' مساحات الأسماء
Imports System
Imports System.Collections
' الفئة listeDePersonnes
Public Class listeDePersonnes
Inherits ArrayList
' لإضافة شخص إلى القائمة
Public Shadows Sub Add(ByVal p As Object)
' يجب أن يكون p شخصًا
If Not TypeOf (p) Is personne Then
Throw New Exception("L'objet ajouté (" + p.ToString + ") n'est pas une personne")
Else
MyBase.Add(p)
End If
End Sub
' مفهرس
Default Public Shadows Property Index(ByVal i As Integer) As personne
Get
Return CType(MyBase.Item(i), personne)
End Get
Set(ByVal Value As personne)
MyBase.Item(i) = Value
End Set
End Property
' toString
Public Overrides Function ToString() As String
' تُرجع (عنصر1، عنصر2، ...، عنصرn)
Dim liste As String = "("
Dim i As Integer
' يتم تصفح المصفوفة الديناميكية
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' العنصر الأخير
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
End Class
تحتوي الفئة على الطرق والخصائص التالية:
تُرجع سلسلة أحرف «تمثل» محتوى القائمة | |
طريقة تسمح بإضافة شخص إلى القائمة | |
خاصية مفهرسة افتراضيًا تعطي الشخص i في القائمة |
لننظر إلى النقاط الجديدة:
تم إنشاء طريقة جديدة [Add].
' لإضافة شخص إلى القائمة
Public Shadows Sub Add(ByVal p As Object)
' يجب أن يكون p شخصًا
If Not TypeOf (p) Is personne Then
Throw New Exception("L'objet ajouté (" + p.ToString + ") n'est pas une personne")
Else
MyBase.Add(p)
End If
End Sub
يوجد بالفعل إجراء في الفئة الأم [ArrayList] بنفس التوقيع، ومن هنا جاءت الكلمة الرئيسية [Shadows] للإشارة إلى أن الإجراء الجديد يحل محل الإجراء الموجود في الفئة الأم. تتحقق الطريقة [Add] في الفئة الفرعية من أن الكائن المضاف هو بالفعل من النوع [personne] أو مشتق باستخدام الدالة [TypeOf]. وإذا لم يكن الأمر كذلك، يتم إلقاء استثناء باستخدام الأمر [Throw]. وإذا كان الكائن المضاف من النوع [personne] بالفعل، يتم استخدام الطريقة [Add] الخاصة بالفئة الأساسية لإجراء الإضافة.
يتم إنشاء خاصية مفهرسة:
' مؤشر
Default Public Shadows Property Index(ByVal i As Integer) As personne
Get
Return CType(MyBase.Item(i), personne)
End Get
Set(ByVal Value As personne)
MyBase.Item(i) = Value
End Set
End Property
توجد بالفعل خاصية افتراضية تسمى [Item] في الفئة الأساسية بنفس التوقيع. لذلك، لا بد من استخدام الكلمة الرئيسية [Shadows] للإشارة إلى أن الخاصية المفهرسة الجديدة [Index] ستقوم «بإخفاء» الخاصية الموجودة في الفئة الأساسية [Item]. وتجدر الإشارة إلى أن هذا ينطبق حتى لو لم تكن الخاصيتان تحملان نفس الاسم. تسمح الخاصية [Index] بالإشارة إلى الشخص رقم i في القائمة. وهي تعتمد على الخاصية [Item] للفئة الأساسية للوصول إلى العنصر رقم i في الكائن الأساسي [ArrayList]. يتم إجراء تغييرات في النوع لمراعاة أن الخاصية [Item] تعمل مع عناصر من النوع [Object]، في حين أن الخاصية [Index] تعمل مع عناصر من النوع [personne].
وأخيرًا، نقوم بإعادة تعريف (Overrides) الطريقة [ToString] للفئة [ArrayList]:
' toString
Public Overrides Function ToString() As String
' تُرجع (عنصر1، عنصر2، ...، عنصرn)
Dim liste As String = "("
Dim i As Integer
' يتم تصفح المصفوفة الديناميكية
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' العنصر الأخير
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
تُرجع هذه الطريقة سلسلة أحرف بالصيغة "(e1,e2,...,en)" حيث ei هي عناصر القائمة. تجدر الإشارة إلى الترميز [Me(i)] الذي يشير إلى العنصر رقم i من الكائن الحالي [Me]. يتم استخدام الخاصية المفهرسة افتراضيًا. وبالتالي، فإن [Me(i)] تعادل [Me.Index(i)].
يتم وضع كود الفئة في الملف [lstpersonnes2.vb] وتجميعه:
dos>dir
11/03/2004 18:26 3 584 enseignant.dll
12/03/2004 15:45 970 lstpersonnes2.vb
11/03/2004 18:26 4 096 personne.dll
dos>vbc /r:personne.dll /r:enseignant.dll /t:library lstpersonnes2.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
11/03/2004 18:26 3 584 enseignant.dll
12/03/2004 15:50 3 584 lstpersonnes2.dll
12/03/2004 15:45 970 lstpersonnes2.vb
11/03/2004 18:26 4 096 personne.dll
يتم إنشاء برنامج اختبار:
' خيارات
Option Explicit On
Option Strict On
' مساحات الأسماء
Imports System
Imports System.Collections
Module test
Sub Main()
' إنشاء قائمة فارغة بالأشخاص
Dim liste As listeDePersonnes = New listeDePersonnes
' إنشاء أشخاص
Dim p1 As personne = New personne("paul", "chenou", 31)
Dim p2 As personne = New personne("nicole", "chenou", 11)
Dim e1 As enseignant = New enseignant("jacques", "sileau", 33, 61)
' ملء القائمة
liste.Add(p1)
liste.Add(p2)
liste.Add(e1)
' عرض القائمة
Console.Out.WriteLine(liste.ToString)
' إضافة كيان غير «شخص»
Try
liste.Add(4)
Catch e As Exception
Console.Error.WriteLine(e.Message)
End Try
End Sub
End Module
يتم ترجمته:
dos>vbc /r:personne.dll /r:enseignant.dll /r:lstpersonnes2.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
11/03/2004 18:26 3 584 enseignant.dll
12/03/2004 15:50 3 584 lstpersonnes2.dll
12/03/2004 15:45 970 lstpersonnes2.vb
11/03/2004 18:26 4 096 personne.dll
12/03/2004 15:50 3 584 test.exe
12/03/2004 15:49 623 test.vb
ثم تم تنفيذه:
dos>test
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
([personne(paul,chenou,31)],[personne(nicole,chenou,11)],[enseignant(personne(jacques,sileau,33),61)])
L'objet ajouté (4) n'est pas une personne
قد نرغب في كتابة
حيث يكون l من النوع [listeDePersonnes]. هنا، نريد فهرسة القائمة l ليس برقم العنصر بل باسم الشخص. ولذلك، نُعرِّف خاصية جديدة مفهرسة افتراضيًا:
' مفهرس آخر
Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
Get
' البحث عن الشخص الذي اسمه N
Dim i As Integer
For i = 0 To Count - 1
If CType(Me(i), personne).nom = N Then
Return i
End If
Next i
Return -1
End Get
End Property
السطر الأول
Default Public Shadows ReadOnly Property Index(ByVal N As String) As Integer
يشير إلى إنشاء خاصية مفهرسة افتراضية جديدة. يجب أن تحمل جميع الخصائص الافتراضية الاسم نفسه، وهو في هذه الحالة [Index]. تقوم الخاصية الجديدة [Index] بفهرسة الفئة listeDePersonnes باستخدام سلسلة الأحرف N. ناتج listeDePersonnes(N) هو عدد صحيح. وسيكون هذا العدد الصحيح هو الموضع في القائمة للشخص الذي يحمل الاسم N أو -1 إذا لم يكن هذا الشخص موجودًا في القائمة. يتم تعريف الخاصية get فقط، مما يمنع الكتابة listeDePersonnes ("nom")=valeur التي كانت ستتطلب تعريف الخاصية set). ومن هنا تأتي الكلمة الرئيسية [ReadOnly]. الكلمة الرئيسية [Shadows] ضرورية لإخفاء الخاصية الافتراضية للفئة الأساسية (على الرغم من أنها لا تحمل نفس التوقيع).
في نص get، يتم البحث في قائمة الأشخاص عن الاسم N الذي تم تمريره كمعلمة. إذا تم العثور عليه في الموضع i، يتم إرجاع i، وإلا يتم إرجاع -1.
قد يكون برنامج الاختبار الجديد كما يلي:
' خيارات
Option Strict On
Option Explicit On
' مساحات الأسماء
Imports System
' صفحة اختبار
Module test
Sub Main()
' قائمة بالأشخاص
Dim l As New listeDePersonnes
' إضافة أشخاص
l.Add(New personne("jean", "dumornet", 10))
l.Add(New personne("pauline", "duchemin", 12))
' العرض
Console.Out.WriteLine(("l=" + l.ToString))
l.Add(New personne("jacques", "tartifume", 27))
Console.Out.WriteLine(("l=" + l.ToString))
' تعديل العنصر 1
l(1) = New personne("sylvie", "cachan", 5)
' عرض العنصر 1
Console.Out.WriteLine(("l[1]=" + l(1).ToString))
' عرض القائمة l
Console.Out.WriteLine(("l=" + l.ToString))
' البحث عن أشخاص
Dim noms() As String = New [String]() {"cachan", "inconnu"}
Dim i As Integer
For i = 0 To noms.Length - 1
Dim inom As Integer = l(noms(i))
If inom <> -1 Then
Console.Out.WriteLine(("personne(" & noms(i) & ")=" & l(inom).ToString))
Else
Console.Out.WriteLine(("personne(" + noms(i) + ") n'existe pas"))
End If
Next i
End Sub
End Module
يُعطِي التنفيذ النتائج التالية:
personne(string, string, int)
Construction personne(string, string, int)
l=([personne(jean,dumornet,10)],[personne(pauline,duchemin,12)])
Construction personne(string, string, int)
l=([personne(jean,dumornet,10)],[personne(pauline,duchemin,12)],[personne(jacques,tartifume,27)])
Construction personne(string, string, int)
l[1]=personne(sylvie,cachan,5)
l=([personne(jean,dumornet,10)],[personne(sylvie,cachan,5)],[personne(jacques,tartifume,27)])
personne(cachan)=personne(sylvie,cachan,5)
personne(inconnu) n'existe pas
3.4. الهياكل
البنية VB.NET مستمدة مباشرة من بنية لغة C وهي قريبة جدًا من الفئة. تُعرَّف البنية على النحو التالي:
Structure spersonne
' السمات
...
' الخصائص
...
' المنشئات
...
' الأساليب
End Structure
على الرغم من تشابه الصيغة، هناك اختلافات مهمة بين الفئة والبنية. فمفهوم التوريث، على سبيل المثال، غير موجود في البنى. إذا كنا نكتب فئة لا يجب أن تكون مشتقة، فما هي الاختلافات بين البنية والفئة التي ستساعدنا في الاختيار بينهما؟ لنستعين بالمثال التالي لاكتشاف ذلك:
' خيارات
Option Strict On
Option Explicit On
' مساحات الأسماء
Imports System
' هيكل spersonne
Structure spersonne
Public nom As String
Public age As Integer
End Structure
' فئة cpersonne
Class cpersonne
Public nom As String
Public age As Integer
End Class
' وحدة اختبار
Public Module test
Sub Main()
' شخص p1
Dim sp1 As spersonne
sp1.nom = "paul"
sp1.age = 10
Console.Out.WriteLine(("sp1=spersonne(" & sp1.nom & "," & sp1.age & ")"))
' شخص p2
Dim sp2 As spersonne = sp1
Console.Out.WriteLine(("sp2=spersonne(" & sp2.nom & "," & sp2.age & ")"))
' تم تعديل sp2
sp2.nom = "nicole"
sp2.age = 30
' التحقق من sp1 و sp2
Console.Out.WriteLine(("sp1=cpersonne(" & sp1.nom & "," & sp1.age & ")"))
Console.Out.WriteLine(("sp2=cpersonne(" & sp2.nom & "," & sp2.age & ")"))
' شخص cp1
Dim cp1 As New cpersonne
cp1.nom = "paul"
cp1.age = 10
Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
' شخص c P2
Dim cp2 As cpersonne = cp1
Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
' تم تعديل P2
cp2.nom = "nicole"
cp2.age = 30
' التحقق من P1 و P2
Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
End Sub
End Module
إذا قمنا بتشغيل هذا البرنامج، فسنحصل على النتائج التالية:
sp1=spersonne(paul,10)
sp2=spersonne(paul,10)
sp1=cpersonne(paul,10)
sp2=cpersonne(nicole,30)
cP1=cpersonne(paul,10)
cP2=cpersonne(paul,10)
cP1=cpersonne(nicole,30)
cP2=cpersonne(nicole,30)
حيث كنا نستخدم في الصفحات السابقة من هذا الفصل فئة personne، فإننا نستخدم الآن بنية spersonne:
' هيكل الشخص
Structure spersonne
Public nom As String
Public age As Integer
End Structure
البيان
Dim sp1 As spersonne
يُنشئ بنية (الاسم، العمر) وقيمة sp1 هي هذه البنية نفسها.
الإعلان
Dim cp1 As New cpersonne
يُنشئ كائنًا [cpersonne] (وهو ما يعادل بشكل عام البنية التي لدينا) ويكون cp1 عندئذٍ عنوان (مرجع) هذا الكائن.
لنلخص
- في حالة البنية، تكون قيمة sp1 هي البنية نفسها
- في حالة الفئة، تكون قيمة p1 هي عنوان الكائن الذي تم إنشاؤه
![]() |
![]() |
عندما نكتب في البرنامج
Dim sp2 As spersonne = sp1
يتم إنشاء بنية جديدة (الاسم، العمر) وتهيئتها بقيمة p1، أي البنية نفسها.
![]() |
وبالتالي، يتم نسخ بنية sp1 في sp2. وهذا ما يُعرف بنسخ القيم.
الأمر
Dim cp2 As cpersonne = cp1
تعمل بشكل مختلف. يتم نسخ قيمة cp1 إلى cp2، ولكن بما أن هذه القيمة هي في الواقع عنوان الكائن، فإن الكائن نفسه لا يتم تكراره. بل يوجد ببساطة مرجعان يشيران إليه:
![]() |
في حالة البنية، إذا قمنا بتعديل قيمة sp2، فإننا لا نعدل قيمة sp1، وهو ما يوضحه البرنامج. أما في حالة الكائن، فإذا قمنا بتعديل الكائن الذي يشير إليه cp2، فإن الكائن الذي يشير إليه cp1 يتغير أيضًا لأنهما متطابقان. وهذا ما توضحه أيضًا نتائج البرنامج.
وبالتالي، يمكننا استخلاص ما يلي من هذه التفسيرات:
- قيمة المتغير من نوع البنية هي البنية نفسها
- قيمة المتغير من نوع الكائن هي عنوان الكائن المشار إليه
وبمجرد فهم هذا الاختلاف الأساسي، يتضح أن البنية تشبه إلى حد كبير الفئة، كما يوضح المثال الجديد التالي:
' خيارات
Option Strict On
Option Explicit On
' مساحات الأسماء
Imports System
' هيكل الشخص
Structure personne
' السمات
Private _nom As String
Private _age As Integer
' الخصائص
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
_age = value
End Set
End Property
' المنشئ
Public Sub New(ByVal NOM As String, ByVal AGE As Integer)
_nom = NOM
_age = AGE
End Sub 'New
' TOSTRING
Public Overrides Function ToString() As String
Return "personne(" & nom & "," & age & ")"
End Function
End Structure
' وحدة اختبار
Module test
Sub Main()
' شخص p1
Dim p1 As New personne("paul", 10)
Console.Out.WriteLine(("p1=" & p1.ToString))
' شخص p2
Dim p2 As personne = p1
Console.Out.WriteLine(("p2=" & p2.ToString))
' تم تعديل p2
p2.nom = "nicole"
p2.age = 30
' التحقق من p1 و p2
Console.Out.WriteLine(("p1=" & p1.ToString))
Console.Out.WriteLine(("p2=" & p2.ToString))
End Sub
End Module
ونحصل على نتائج التنفيذ التالية:
الفرق الوحيد الملحوظ هنا بين البنية والفئة هو أنه في حالة الفئة، كان الكائنان p1 و p2 سيحصلان على نفس القيمة في نهاية البرنامج، وهي قيمة p2.
3.5. الواجهات
الواجهة هي مجموعة من النماذج الأولية للطرق أو الخصائص التي تشكل عقدًا. تلتزم الفئة التي تقرر تنفيذ واجهة ما بتوفير تنفيذ لجميع الطرق المحددة في الواجهة. ويقوم المُجمِّع بالتحقق من هذا التنفيذ. وفيما يلي، على سبيل المثال، تعريف الواجهة Istats:
سيتم إعلان أي فئة تُنفذ هذه الواجهة على النحو التالي:
public class C
Implements Istats
...
function moyenne() as Double Implements Istats.moyenne
...
end function
function écartType () as Double Implements Istats. écartType
...
end function
end class
يجب تعريف الطريقتين [moyenne] و [écartType] في الفئة C. لننظر إلى الكود التالي:
' خيارات
Option Strict On
Option Explicit On
' مساحات الأسماء
Imports System
' البنية
Public Structure élève
Public _nom As String
Public _note As Double
' المنشئ
Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
Me._nom = NOM
Me._note = NOTE
End Sub
End Structure
' ملاحظات الفئة
Public Class notes
' السمة
Protected _matière As String
Protected _élèves() As élève
' منشئ
Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
' تخزين بيانات الطلاب والمواد الدراسية
Me._matière = MATIERE
Me._élèves = ELEVES
End Sub
' ToString
Public Overrides Function ToString() As String
Dim valeur As String = "matière=" + _matière + ", notes=("
Dim i As Integer
' يتم تجميع جميع الدرجات
For i = 0 To (_élèves.Length - 1) - 1
valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "],"
Next i
'آخر علامة
If _élèves.Length <> 0 Then
valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "]"
End If
valeur += ")"
' النهاية
Return valeur
End Function
End Class
تجمع الفئة notes الدرجات الخاصة بفصل دراسي في مادة ما:
Public Class notes
' السمة
Protected _matière As String
Protected _élèves() As élève
يتم تعريف السمات protected بحيث يمكن الوصول إليها من فئة مشتقة. النوع élève هو بنية تخزن اسم الطالب ودرجته في المادة:
Public Structure élève
Public _nom As String
Public _note As Double
' المنشئ
Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
Me._nom = NOM
Me._note = NOTE
End Sub
End Structure
قررنا اشتقاق هذه الفئة notes إلى فئة notesStats التي ستحتوي على سمتين إضافيتين، هما المتوسط والانحراف المعياري للدرجات:
Public Class notesStats
Inherits notes
Implements Istats
' السمات
Private _moyenne As Double
Private _écartType As Double
تنفذ الفئة notesStats الواجهة Istats التالية:
وهذا يعني أن الفئة notesStats يجب أن تحتوي على طريقتين تُسميان moyenne و écartType، مع التوقيع المحدد في الواجهة Istats. الفئة notesStats هي كما يلي:
' خيارات
Option Strict On
Option Explicit On
' مساحات الأسماء
Imports System
Public Class notesStats
Inherits notes
Implements Istats
' السمات
Private _moyenne As Double
Private _écartType As Double
' المنشئ
Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
MyBase.New(MATIERE, ELEVES)
' حساب متوسط الدرجات
Dim somme As Double = 0
Dim i As Integer
For i = 0 To ELEVES.Length - 1
somme += ELEVES(i)._note
Next i
If ELEVES.Length <> 0 Then
_moyenne = somme / ELEVES.Length
Else
_moyenne = -1
End If
' الانحراف المعياري
Dim carrés As Double = 0
For i = 0 To ELEVES.Length - 1
carrés += Math.Pow(ELEVES(i)._note - _moyenne, 2)
Next i
If ELEVES.Length <> 0 Then
_écartType = Math.Sqrt((carrés / ELEVES.Length))
Else
_écartType = -1
End If
End Sub
' ToString
Public Overrides Function ToString() As String
Return MyBase.ToString() & ",moyenne=" & _moyenne & ",écart-type=" & _écartType
End Function 'ToString
' طرق واجهة Istats
Public Function moyenne() As Double Implements Istats.moyenne
' إرجاع متوسط الدرجات
Return _moyenne
End Function
Public Function écartType() As Double Implements Istats.écartType
' إرجاع الانحراف المعياري
Return _écartType
End Function
End Class
يتم حساب المتوسط _moyenne والانحراف المعياري _ecartType فور إنشاء الكائن. وبالتالي، فإن الطريقتين moyenne و écartType لا يتعين عليهما سوى إرجاع قيمة السمتين _moyenne و _ecartType. وتُرجع كلتا الطريقتين القيمة -1 إذا كان جدول الطلاب فارغًا.
فئة الاختبار التالية:
' خيارات
Option Strict On
Option Explicit On
' مساحات الأسماء
Imports System
Module test
Sub Main()
' بعض الطلاب ودرجاتهم
Dim ELEVES() As élève = {New élève("paul", 14), New élève("nicole", 16), New élève("jacques", 18)}
' يتم تسجيلها في كائن «الدرجات»
Dim anglais As New notes("anglais", ELEVES)
' ويتم عرضها
Console.Out.WriteLine((anglais.ToString))
' وكذلك الأمر بالنسبة للمتوسط والانحراف المعياري
anglais = New notesStats("anglais", ELEVES)
Console.Out.WriteLine((anglais.ToString))
End Sub
End Module
تعطي النتائج التالية:
matière=anglais, notes=([paul,14],[nicole,16],[jacques,18])
matière=anglais, notes=([paul,14],[nicole,16],[jacques,18]),moyenne=16,écart-type=1,63299316185545
كان من الممكن تمامًا أن تقوم الفئة notesStats بتنفيذ الطريقتين moyenne و écartType بنفسها دون الإشارة إلى أنها تنفذ الواجهة Istats. ما فائدة الواجهات؟ الفائدة هي التالية: يمكن أن تقبل الدالة كمعلمة شكلية بيانات من نوع واجهة I. وعندئذٍ يمكن لأي كائن من فئة C التي تنفذ الواجهة I أن يكون معلمة فعلية لهذه الدالة. لننظر إلى المثال التالي:
' خيارات
Option Strict On
Option Explicit On
' مساحات الأسماء
Imports System
' واجهة Iexample
Public Interface Iexemple
Function ajouter(ByVal i As Integer, ByVal j As Integer) As Integer
Function soustraire(ByVal i As Integer, ByVal j As Integer) As Integer
End Interface
' فئة أولى
Public Class classe1
Implements Iexemple
Public Function ajouter(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.ajouter
Return a + b + 10
End Function
Public Function soustraire(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.soustraire
Return a - b + 20
End Function
End Class
'فئة ثانية
Public Class classe2
Implements Iexemple
Public Function ajouter(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.ajouter
Return a + b + 100
End Function
Public Function soustraire(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.soustraire
Return a - b + 200
End Function
End Class
تُعرّف الواجهة Iexemple طريقتين هما ajouter و soustraire. تقوم الفئتان classe1 و classe2 بتنفيذ هذه الواجهة. تجدر الإشارة إلى أن هاتين الفئتين لا تقومان بأي شيء آخر، وذلك لتبسيط المثال. والآن لننظر إلى المثال التالي:
' خيارات
Option Strict On
Option Explicit On
' مساحات الأسماء
Imports System
' فئة اختبار
Module test
'حساب
Sub calculer(ByVal i As Integer, ByVal j As Integer, ByVal inter As Iexemple)
Console.Out.WriteLine(inter.ajouter(i, j))
Console.Out.WriteLine(inter.soustraire(i, j))
End Sub
' دالة Main
Sub Main()
' إنشاء كائنين «class1» و«class2»
Dim c1 As New classe1
Dim c2 As New classe2
' استدعاء الدالة الثابتة «حساب»
calculer(4, 3, c1)
calculer(14, 13, c2)
End Sub
End Module
تقبل الدالة calculer كمعلمة عنصرًا من النوع Iexemple. وبالتالي، يمكنها أن تتلقى لهذا المعلمة كائنًا من النوع classe1 أو من النوع classe2. وهذا ما يتم تنفيذه في الإجراء Main مع النتائج التالية:
ونلاحظ إذن أن لدينا هنا خاصية قريبة من تعدد الأشكال الذي رأيناه في الفئات. إذا كانت مجموعة من الفئات Ci غير مرتبطة ببعضها البعض عن طريق التوريث (وبالتالي لا يمكن استخدام تعدد الأشكال الناتج عن التوريث) تقدم مجموعة من الطرق ذات التوقيع نفسه، فقد يكون من المفيد تجميع هذه الطرق في واجهة I ترثها جميع الفئات المعنية. وبذلك يمكن استخدام مثيلات هذه الفئات Ci كمعلمات لدوال تقبل معلمة من النوع I، c.a.d. وهي دوال لا تستخدم سوى طرق الكائنات Ci المُعرَّفة في الواجهة I، ولا تستخدم السمات والطرق الخاصة بالفئات Ci المختلفة. وأخيرًا، تجدر الإشارة إلى أن وراثة الواجهات يمكن أن تكون متعددة، c.a.d. ويمكن كتابة
Public Class classe
Implements I1,I2,...
حيث Ij هي واجهات.
3.6. مساحات الأسماء
لكتابة سطر على الشاشة، نستخدم الأمر
إذا نظرنا إلى تعريف الفئة Console
Namespace: System
Assembly: Mscorlib (in Mscorlib.dll)
نكتشف أنها جزء من مساحة الأسماء System. وهذا يعني أن الفئة Console يجب أن يُشار إليها بـ System.Console، وبالتالي يجب أن نكتب في الواقع:
يمكن تجنب ذلك باستخدام جملة imports:
يُقال إننا نستورد مساحة الأسماء System باستخدام الجملة imports. وعندما يصادف المُجمِّع اسم فئة (في هذه الحالة Console)، فإنه سيبحث عنها في مختلف مساحات الأسماء التي تم استيرادها بواسطة الجمل imports. وهنا سيجد الفئة Console في مساحة الأسماء System. لنلاحظ الآن المعلومة الثانية المرتبطة بالفئة Console:
Assembly: Mscorlib (in Mscorlib.dll)
تشير هذه السطر إلى «التجميع» الذي يوجد فيه تعريف الفئة Console. عند إجراء التجميع خارج Visual Studio.NET، وعند الحاجة إلى تحديد مراجع مختلف ملفات dll التي تحتوي على الفئات المطلوب استخدامها، قد تكون هذه المعلومة مفيدة. تجدر الإشارة إلى أنه للإشارة إلى ملفات dll اللازمة لتجميع فئة ما، نكتب:
عند إنشاء فئة، يمكن إنشاؤها داخل مساحة أسماء. والغرض من مساحات الأسماء هذه هو تجنب تعارض الأسماء بين الفئات عند بيعها على سبيل المثال. لنفترض وجود شركتين E1 و E2 توزعان فئات مجمعة على التوالي في dll، E1.dll و E2.dll. لنفترض أن العميل C يشتري هاتين المجموعتين من الفئات، حيث قامت كلتا الشركتين بتعريف فئة personne. يقوم العميل C بترجمة برنامج على النحو التالي:
إذا كان المصدر prog.vb يستخدم الفئة personne، فلن يتمكن المُجمِّع من تحديد ما إذا كان عليه أخذ الفئة personne من E1.dll أم من E2.dll. وسيُبلغ عن وجود خطأ. إذا حرصت الشركة E1 على إنشاء فئاتها في مساحة أسماء تسمى E1، وحرصت الشركة E2 على إنشاء فئاتها في مساحة أسماء تسمى E2، فإن الفئتين personne ستسميان عندئذٍ E1.personne و E2.personne. سيتعين على العميل استخدام إما E1.personne أو E2.personne في فئاته، ولكن ليس personne. تسمح مساحة الأسماء بإزالة الغموض. لإنشاء فئة في مساحة أسماء، نكتب:
Namespace istia.st
Public Class personne
' تعريف الفئة
...
end Class
end Namespace
على سبيل المثال، لنقم بإنشاء فئتنا personne التي درسناها سابقًا في مساحة أسماء. سنختار istia.st كمساحة أسماء. تصبح الفئة personne كما يلي:
' خيارات
Option Strict On
Option Explicit On
' مساحات الأسماء
Imports System
' إنشاء مساحة الاسم istia.st
Namespace istia.st
Public Class personne
' السمات
Private prenom As String
Private nom As String
Private age As Integer
' الطريقة
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' الطريقة
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
End Namespace
يتم ترجمة هذه الفئة إلى personne.dll:
dos>vbc /t:library personne.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
الآن دعونا نستخدم الفئة personne في فئة اختبار:
' خيارات
Option Strict On
Option Explicit On
' مساحات الأسماء
Imports System
Imports istia.st
' صفحة اختبار
Public Module test
Sub Main()
Dim p1 As New personne
p1.initialise("Jean", "Dupont", 30)
p1.identifie()
End Sub
End Module
لتجنب كتابة
Dim p1 As New istia.st.personne
قمنا باستيراد مساحة الأسماء istia.st باستخدام جملة imports:
Imports istia.st
الآن لنقم بتجميع برنامج الاختبار:
dos>dir
12/03/2004 18:06 3 584 personne.dll
11/03/2004 18:27 610 personne.vb
12/03/2004 18:05 254 test.vb
dos>vbc /r:personne.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
12/03/2004 18:06 3 584 personne.dll
11/03/2004 18:27 610 personne.vb
12/03/2004 18:08 3 072 test.exe
12/03/2004 18:05 254 test.vb
ينتج عن ذلك ملف test.exe الذي يعطي النتائج التالية عند تنفيذه:
3.7. المثال IMPOTS
نستأنف حساب الضريبة الذي تمت دراسته بالفعل في الفصل السابق ونعالجه باستخدام فئة. لنتذكر المشكلة:
نضع أنفسنا في الحالة المبسطة لمكلف لا يملك سوى راتبه الوحيد ليصرح عنه:
- نحسب عدد حصص الموظف nbParts=nbEnfants/2 +1 إذا كان غير متزوج، nbEnfants/2+2 إذا كان متزوجًا، حيث nbEnfants هو عدد أطفاله.
- وإذا كان لديه ثلاثة أطفال على الأقل، يحصل على نصف حصة إضافية
- يُحسب دخله الخاضع للضريبة R=0.72*S حيث S هو راتبه السنوي
- يُحسب معامل الأسرة الخاص به QF = R/nbParts
- نحسب ضريبته I. لننظر إلى الجدول التالي:
12620.0 | 0 | 0 |
13190 | 0.05 | 631 |
15640 | 0.1 | 1290.5 |
24740 | 0.15 | 2072.5 |
31810 | 0.2 | 3309.5 |
39970 | 0.25 | 4900 |
48360 | 0.3 | 6898.5 |
55790 | 0.35 | 9316.5 |
92970 | 0.4 | 12106 |
127860 | 0.45 | 16754.5 |
151250 | 0.50 | 23147.5 |
172040 | 0.55 | 30710 |
195000 | 0.60 | 39312 |
0 | 0.65 | 49062 |
يحتوي كل سطر على 3 حقول. لحساب الضريبة I، نبحث عن أول سطر حيث QF <= الحقل 1. على سبيل المثال، إذا كان QF = 23000، فسنجد السطر
ويكون الضريبة I عندئذٍ مساوية لـ 0.15*R - 2072.5*nbParts. إذا كان QF بحيث لا تتحقق العلاقة QF<=champ1 أبدًا، فسيتم استخدام معاملات السطر الأخير. وهنا:
مما يعطي الضريبة I=0.65*R - 49062*nbParts.
سيتم تعريف فئة «impot» على النحو التالي:
' خيارات
Option Strict On
Option Explicit On
' مساحات الأسماء
Imports System
Public Class impot
' البيانات اللازمة لحساب الضريبة
' مستمدة من مصدر خارجي
Private limites(), coeffR(), coeffN() As Decimal
' الشركة المصنعة
Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
' يتم التحقق من أن الجداول الثلاثة لها نفس الحجم
Dim OK As Boolean = LIMITES.Length = COEFFR.Length And LIMITES.Length = COEFFN.Length
If Not OK Then
Throw New Exception("Les 3 tableaux fournis n'ont pas la même taille(" & LIMITES.Length & "," & COEFFR.Length & "," & COEFFN.Length & ")")
End If
' لا بأس
Me.limites = LIMITES
Me.coeffR = COEFFR
Me.coeffN = COEFFN
End Sub
' حساب الضريبة
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
' حساب عدد الحصص
Dim nbParts As Decimal
If marié Then
nbParts = CDec(nbEnfants) / 2 + 2
Else
nbParts = CDec(nbEnfants) / 2 + 1
End If
If nbEnfants >= 3 Then
nbParts += 0.5D
End If
' حساب الدخل الخاضع للضريبة و«الناتج العائلي»
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' حساب الضريبة
limites((limites.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limites(i)
i += 1
End While
Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
End Function
End Class
يتم إنشاء كائن «ضريبة» بالبيانات التي تسمح بحساب ضريبة المكلف. هذا هو الجزء الثابت من الكائن. بمجرد إنشاء هذا الكائن، يمكن استدعاء طريقة «calculer» الخاصة به بشكل متكرر، والتي تحسب ضريبة المكلف بناءً على حالته الاجتماعية (متزوج أم لا)، وعدد أطفاله، وراتبه السنوي. قد يكون برنامج الاختبار كما يلي:
' خيارات
Option Strict On
Option Explicit On
' مساحات الأسماء
Imports System
Imports Microsoft.VisualBasic
Module test
Sub Main()
' برنامج تفاعلي لحساب الضريبة
' يقوم المستخدم بإدخال ثلاث بيانات عبر لوحة المفاتيح: متزوج nbEnfants الراتب
' ثم يعرض البرنامج الضريبة المستحقة
Const syntaxe As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
' جداول البيانات اللازمة لحساب الضريبة
Dim limites() As Decimal = {12620D, 13190D, 15640D, 24740D, 31810D, 39970D, 48360D, 55790D, 92970D, 127860D, 151250D, 172040D, 195000D, 0D}
Dim coeffR() As Decimal = {0D, 0.05D, 0.1D, 0.15D, 0.2D, 0.25D, 0.3D, 0.35D, 0.4D, 0.45D, 0.5D, 0.55D, 0.6D, 0.65D}
Dim coeffN() As Decimal = {0D, 631D, 1290.5D, 2072.5D, 3309.5D, 4900D, 6898.5D, 9316.5D, 12106D, 16754.5D, 23147.5D, 30710D, 39312D, 49062D}
' إنشاء كائن «ضريبة»
Dim objImpôt As impot = Nothing
Try
objImpôt = New impot(limites, coeffR, coeffN)
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(1)
End Try
' حلقة لا نهائية
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Long
While True
' يُطلب إدخال معلمات حساب الضريبة
Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
Dim paramètres As String = Console.In.ReadLine().Trim()
' هل هناك ما يجب فعله؟
If paramètres Is Nothing OrElse paramètres = "" Then
Exit While
End If
' التحقق من عدد المعلمات في السطر الذي تم إدخاله
Dim erreur As Boolean = False
Dim args As String() = paramètres.Split(Nothing)
Dim nbParamètres As Integer = args.Length
If nbParamètres <> 3 Then
Console.Error.WriteLine(syntaxe)
erreur = True
End If
' التحقق من صحة المعلمات
If Not erreur Then
' متزوج
marié = args(0).ToLower()
If marié <> "o" And marié <> "n" Then
erreur = True
End If
' nbEnfants
Try
nbEnfants = Integer.Parse(args(1))
If nbEnfants < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
' الراتب
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
End If
' إذا كانت المعلمات صحيحة - يتم حساب الضريبة
If Not erreur Then
Console.Out.WriteLine(("impôt=" & objImpôt.calculer(marié = "o", nbEnfants, salaire) & " F"))
Else
Console.Error.WriteLine(syntaxe)
End If
End While
End Sub
End Module
فيما يلي مثال على تنفيذ البرنامج السابق:
dos>vbc /t:library impots.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dir>dir
12/03/2004 18:24 4 096 impots.dll
12/03/2004 18:20 1 483 impots.vb
12/03/2004 18:21 2 805 test.vb
dos>vbc /r:impots.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
12/03/2004 18:24 4 096 impots.dll
12/03/2004 18:20 1 483 impots.vb
12/03/2004 18:26 6 144 test.exe
12/03/2004 18:21 2 805 test.vb
dos>test
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :x x x
syntaxe : marié nbEnfants salaire
marié : o pour marié, n pour non marié
nbEnfants : nombre d'enfants
salaire : salaire annuel en F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22504 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :





