3. الفئات، الهياكل، الواجهات
3.1. الأشياء من خلال الأمثلة
3.1.1. نظرة عامة
سنستكشف الآن البرمجة الموجهة للكائنات من خلال أمثلة. الكائن هو كيان يحتوي على بيانات تحدد حالته (تسمى الخصائص) ووظائفه (تسمى الطرق). يتم إنشاء الكائن بناءً على قالب يسمى الفئة:
Public Class c1
' attributes
Private p1 As type1
Private p2 As type2
....
' method
Public Sub m1(....)
...
End Sub
' method
Public Function m2(...)
....
End Function
End Class
من الفئة السابقة C1، يمكننا إنشاء العديد من الكائنات O1، O2، … وستتمتع جميعها بالخصائص p1، p2، … والطرق m3، m4، … لكن قيم خصائصها pi ستكون مختلفة، وبالتالي سيكون لكل منها حالته الخاصة. وبالمثل، فإن الإعلان
ينشئ كائنين (المصطلح غير صحيح هنا) من النوع (الفئة) Integer. خاصيتهما الوحيدة هي قيمتهما. إذا كان O1 كائنًا من النوع C1، فإن O1.p1 تشير إلى الخاصية p1 لـ O1 و O1.m1 إلى الطريقة m1 لـ O1. لننظر إلى نموذج كائن أول: فئة Person.
3.1.2. تعريف فئة Person
سيكون تعريف فئة Person كما يلي:
Public Class personne
' attributes
Private prenom As String
Private nom As String
Private age As Integer
' method
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
' method
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
هنا لدينا تعريف فئة، وهي نوع بيانات. عندما ننشئ متغيرات من هذا النوع، نسميها كائنات أو مثيلات فئة. وبالتالي، فإن الفئة هي قالب يتم من خلاله إنشاء الكائنات. يمكن أن تكون عناصر أو حقول الفئة بيانات (سمات) أو أساليب (دوال) أو خصائص. الخصائص هي أساليب خاصة تُستخدم لاسترداد أو تعيين قيمة سمات الكائن. يمكن أن تكون هذه الحقول مصحوبة بإحدى الكلمات الرئيسية الثلاث التالية:
لا يمكن الوصول إلى الحقل الخاص إلا من خلال الطرق الداخلية للفئة | |
يمكن الوصول إلى الحقل العام من خلال أي دالة، سواء تم تعريفه داخل الفئة أم لا | |
لا يمكن الوصول إلى الحقل المحمي إلا من خلال الطرق الداخلية للفئة أو من خلال كائن مشتق (انظر مفهوم الوراثة لاحقًا). |
بشكل عام، تُعلن بيانات الفئة على أنها خاصة، بينما تُعلن أساليبها وخصائصها على أنها عامة. وهذا يعني أن مستخدم الكائن (المبرمج):
- لن يكون لديه وصول مباشر إلى البيانات الخاصة للكائن
- يمكنه استدعاء الأساليب العامة للكائن، خاصة تلك التي تتيح الوصول إلى بياناته الخاصة.
صيغة إعلان الفئة هي كما يلي:
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
ترتيب إعلان السمات الخاصة والمحمية والعامة هو ترتيب تعسفي.
3.1.3. طريقة التهيئة
لنعد إلى فئة [person] المُعلنة على النحو التالي:
Public Class personne
' attributes
Private prenom As String
Private nom As String
Private age As Integer
' method
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
' method
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
ما هو دور طريقة initialize؟ نظرًا لأن lastName و firstName و age هي بيانات خاصة بفئة Person، فإن العبارات التالية:
غير صالحة. يجب علينا تهيئة كائن من نوع Person عبر طريقة عامة. وهذا هو دور طريقة initialize. سنكتب:
صيغة p1.initialize صحيحة لأن initialize عامة.
3.1.4. المشغل الجديد
تسلسل العبارات
غير صحيح. العبارة
يعلن p1 كمرجع إلى كائن من نوع person. هذا الكائن غير موجود بعد، لذا لم يتم تهيئة p1. وكأننا نكتب:
حيث نشير صراحةً باستخدام الكلمة الرئيسية nothing إلى أن المتغير p1 لا يشير بعد إلى أي كائن. وعندما نكتب بعد ذلك
فإننا نستدعي طريقة initialize للكائن الذي يشير إليه p1. ومع ذلك، فإن هذا الكائن لا يوجد بعد، وسيقوم المُترجم بالإبلاغ عن خطأ. لجعل p1 يشير إلى كائن، يجب عليك كتابة:
يؤدي هذا إلى إنشاء كائن Person غير مهيأ: ستكون قيمة السمتين name و first_name، اللتين تشيران إلى كائنات String، هي nothing، وستكون قيمة age هي 0. وبالتالي، هناك تهيئة افتراضية. والآن بعد أن أصبح p1 يشير إلى كائن، فإن عبارة التهيئة لهذا الكائن
صالحًا.
3.1.5. الكلمة الرئيسية Me
دعونا نلقي نظرة على كود طريقة *Initialize*:
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.firstName = P أن الخاصية firstName للكائن الحالي (Me) تُعيَّن لها القيمة P. تشير الكلمة الرئيسية Me إلى الكائن الحالي: الكائن الذي يتم فيه تنفيذ الأسلوب. كيف نعرف ذلك؟ لنلقِ نظرة على كيفية تهيئة الكائن المشار إليه بواسطة p1 في البرنامج المستدعي:
يتم استدعاء طريقة initialize الخاصة بالكائن p1. عندما تتم الإشارة إلى الكائن Me داخل هذه الطريقة، فإنها تشير في الواقع إلى الكائن p1. كان من الممكن أيضًا كتابة طريقة initialize على النحو التالي:
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
' attributes
Private prenom As String
Private nom As String
Private age As Integer
' method
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
' method
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. استخدام ملف فئة مُجمَّع (تجميع)
لاحظ أنه في المثال السابق، هناك فئتان في برنامج الاختبار الخاص بنا: فئتا person و test1. وهناك طريقة أخرى للمتابعة:
-
قم بتجميع فئة Person في ملف منفصل يسمى تجميع. هذا الملف له امتداد .dll
-
نقوم بترجمة فئة test1 من خلال الإشارة إلى التجميع الذي يحتوي على فئة person.
يصبح الملفان المصدران كما يلي:
test.vb | |
person2.vb | |
يتم ترجمة فئة Person بواسطة العبارة التالية:
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. وعندما يجد إشارة إلى فئة Person في ملف المصدر test.vb — وهي فئة غير مُعلَّنة في ملف المصدر test.vb — سيبحث عن فئة Person في ملفات .dll المشار إليها بواسطة خيار /r. وسيجد فئة Person هنا في تجميع Person2.dll. كان بإمكاننا تضمين فئات أخرى في هذا التجميع. لاستخدام ملفات فئات متعددة تم تجميعها أثناء التجميع، سنكتب:
يؤدي تشغيل البرنامج test1.exe إلى النتائج التالية:
3.1.8. تقوم طريقة أخرى بتهيئة
لنواصل مع فئة Person ونضيف إليها الطريقة التالية:
' method
Public Sub initialise(ByVal P As personne)
prenom = P.prenom
nom = P.nom
Me.age = P.age
End Sub
لدينا الآن طريقتان باسم Initialize: وهذا صحيح طالما أنهما تقبلان معلمات مختلفة. وهذا هو الحال هنا. المعلمة الآن هي مرجع P إلى Person. ثم يتم تعيين سمات Person P إلى الكائن الحالي (Me). لاحظ أن طريقة initialize لها وصول مباشر إلى سمات الكائن P على الرغم من أنها من النوع private. وهذا صحيح دائمًا: الكائن O1 من الفئة C لديه دائمًا وصول إلى سمات الكائنات من نفس الفئة C. فيما يلي اختبار لفئة Person الجديدة، التي تم تجميعها في Person.dll كما هو موضح سابقًا:
' options
Option Explicit On
Option Strict On
' namespaces
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(). ثم يتم تهيئة سمات الكائن بالقيم الافتراضية. هذا ما حدث في البرامج السابقة، حيث كتبنا:
لنقم بإنشاء منشئين لفئة Person الخاصة بنا:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' the person class
Public Class personne
' attributes
Private prenom As String
Private nom As String
Private age As Integer
' manufacturers
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
' object initialization methods
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
' method
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
تقوم كلتا المنشئتين لدينا ببساطة باستدعاء طرق التهيئة المقابلة. لاحظ أنه عندما تظهر، على سبيل المثال، صيغة initialise(P) في منشئ، يقوم المُترجم بترجمتها إلى Me.initialise(P). وبالتالي، يتم استدعاء طريقة initialise في المنشئ للعمل على الكائن الذي يشير إليه Me، أي الكائن الحالي، الذي يجري إنشاؤه. وإليك برنامج اختبار قصير:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test pg
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. مراجع الكائنات
ما زلنا نستخدم نفس فئة Person. يصبح برنامج الاختبار كما يلي:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test pg
Module test
Sub Main()
' p1
Dim p1 As New personne("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identifie()
' p2 references the same object as p1
Dim p2 As personne = p1
Console.Out.Write("p2=")
p2.identifie()
' p3 references an object that will be a copy of the object referenced by p1
Dim p3 As New personne(p1)
Console.Out.Write("p3=")
p3.identifie()
' change the state of the object referenced by p1
p1.initialise("Micheline", "Benoît", 67)
Console.Out.Write("p1=")
p1.identifie()
' as p2=p1, the object referenced by p2 must have changed state
Console.Out.Write("p2=")
p2.identifie()
' as p3 does not reference the same object as p1, the object referenced by p3 must not have changed
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 إلى الكائن Person("Jean", "Dupont", 30) ولكنها ليست الكائن نفسه. في لغة C، يمكننا القول إنها مؤشر، أي عنوان الكائن الذي تم إنشاؤه. إذا كتبنا بعد ذلك:
فليس الكائن person("Jean","Dupont",30) هو الذي يتم تعديله؛ بل إن مرجع p1 هو الذي يتغير قيمته. وسيُفقد الكائن person("Jean","Dupont",30) إذا لم يشار إليه بواسطة أي متغير آخر.
عندما نكتب:
نقوم بتهيئة المؤشر p2: فهو "يشير" إلى نفس الكائن (يشير إلى نفس الكائن) الذي يشير إليه المؤشر p1. وبالتالي، إذا قمنا بتعديل الكائن الذي "يشير إليه" (أو المشار إليه) p1، فإننا نقوم بتعديل الكائن المشار إليه بواسطة p2.
عندما نكتب:
يتم إنشاء كائن جديد، وهو نسخة من الكائن الذي يشير إليه p1. وسيشير p3 إلى هذا الكائن الجديد. إذا قمت بتعديل الكائن الذي «يشير إليه» (أو يُشار إليه) p1، فإنك لا تُغير الكائن الذي يشير إليه p3 بأي شكل من الأشكال. وهذا ما تظهره النتائج.
3.1.11. الكائنات المؤقتة
في تعبير ما، يمكنك استدعاء منشئ الكائن بشكل صريح: يتم إنشاء الكائن، ولكن لا يمكنك الوصول إليه (لتعديله، على سبيل المثال). يتم إنشاء هذا الكائن المؤقت لغرض تقييم التعبير ثم يتم التخلص منه. سيتم استرداد مساحة الذاكرة التي شغلها تلقائيًا لاحقًا بواسطة برنامج يسمى "جامع القمامة"، والذي يتمثل دوره في استرداد مساحة الذاكرة التي تشغلها الكائنات التي لم تعد مرجعية لها بيانات البرنامج. ضع في اعتبارك برنامج الاختبار الجديد التالي:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' test pg
Module test
Sub Main()
Dim p As New personne(New personne("Jean", "Dupont", 30))
p.identifie()
End Sub
End Module
ودعونا نعدل منشئات فئة Person بحيث تعرض رسالة:
' manufacturers
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. طرق قراءة وكتابة السمات الخاصة
نضيف الطرق اللازمة إلى فئة Person لقراءة أو تعديل حالة سمات الكائنات:
Imports System
Public Class personne
' attributes
Private prenom As [String]
Private nom As [String]
Private age As Integer
' manufacturers
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
' identifies
Public Sub identifie()
Console.Out.WriteLine((prenom + "," + nom + "," + age))
End Sub
' accessors
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
'modifiers
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
نختبر الفئة الجديدة باستخدام البرنامج التالي:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' test pg
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. الخصائص
هناك طريقة أخرى للوصول إلى سمات الفئة: عن طريق إنشاء خصائص. تسمح لنا هذه الخصائص بالتعامل مع السمات الخاصة كما لو كانت عامة. انظر إلى فئة Person التالية، حيث تم استبدال أدوات الحصول والتعيين السابقة بخصائص للقراءة والكتابة:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' class person
Public Class personne
' attributes
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' manufacturers
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
' identifies
Public Sub identifie()
Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
End Sub
' properties
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)
' valid age?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
End Class
تسمح لك الخاصية بقراءة (get) أو تعيين (set) قيمة السمة. في مثالنا، قمنا بوضع شرطة سفلية (_) قبل أسماء السمات بحيث تكون للخصائص نفس أسماء السمات الأولية. وذلك لأن الخاصية لا يمكن أن تحمل نفس اسم السمة التي تديرها؛ وإلا، فسيكون هناك تعارض في التسمية داخل الفئة. لذلك، قمنا بتسمية سماتنا _firstName و_lastName و_age وقمنا بتعديل المنشئات والطرق وفقًا لذلك. ثم أنشأنا ثلاث خصائص: lastName وfirstName و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؟ انظر إلى برنامج الاختبار التالي:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test pg
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
في العبارة
نحاول استرداد قيم خصائص first_name و last_name و age للشخص P. يتم بعد ذلك استدعاء طريقة get لهذه الخصائص والتي تعيد قيمة السمة التي تديرها.
في العبارة
نريد تعيين قيمة الخاصية age. ثم يتم استدعاء طريقة set لهذه الخاصية. وستتلقى 56 في معلمة القيمة الخاصة بها.
يُقال إن الخاصية P لفئة C التي تُعرّف طريقة get فقط هي خاصية للقراءة فقط. إذا كان c كائنًا من فئة C، فسيتم رفض العملية c.P=value من قِبل المُترجم.
يؤدي تنفيذ برنامج الاختبار السابق إلى النتائج التالية:
وبالتالي، تسمح لنا الخصائص بمعالجة السمات الخاصة كما لو كانت عامة.
3.1.14. طرق وسمات الفئة
لنفترض أننا نريد حساب عدد كائنات [person] التي تم إنشاؤها في أحد التطبيقات. يمكننا إدارة العداد بأنفسنا، لكننا نخاطر بنسيان الكائنات المؤقتة التي يتم إنشاؤها هنا وهناك. يبدو أنه من الأكثر أمانًا تضمين تعليمات في منشئات فئة [person] تعمل على زيادة العداد. تكمن المشكلة في تمرير مرجع إلى هذا العداد حتى تتمكن المنشئة من زيادته: نحتاج إلى تمرير معلمة جديدة إليها. يمكننا أيضًا تضمين العداد في تعريف الفئة. نظرًا لأنه سمة للفئة نفسها وليس لكائن معين من تلك الفئة، فإننا نعلنه بشكل مختلف باستخدام الكلمة الرئيسية Shared:
للإشارة إليها، نكتب person._nbPeople لإظهار أنها سمة تابعة لفئة Person نفسها. هنا، أنشأنا سمة خاصة لا يمكن الوصول إليها مباشرة من خارج الفئة. لذلك، نقوم بإنشاء خاصية عامة لتوفير الوصول إلى سمة الفئة nbPeople. لإرجاع قيمة nbPeople، لا تحتاج طريقة Get لهذه الخاصية إلى كائن Person محدد: في الواقع، _nbPeople ليست سمة لكائن محدد؛ بل هي سمة للفئة بأكملها. لذلك، نحتاج إلى خاصية يتم إعلانها أيضًا على أنها Shared:
والتي سيتم استدعاؤها من الخارج باستخدام صيغة person.nbPeople. يتم تعريف الخاصية على أنها للقراءة فقط (ReadOnly) لأنها لا توفر طريقة تعيين. إليك مثال على ذلك. تصبح فئة Person كما يلي:
Option Explicit On
Option Strict On
' namespaces
Imports System
' class
Public Class personne
' class attributes
Private Shared _nbPersonnes As Long = 0
' instance attributes
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' manufacturers
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
' one more person
_nbPersonnes += 1
Me._prenom = P
Me._nom = N
Me._age = age
End Sub
Public Sub New(ByVal P As personne)
' one more person
_nbPersonnes += 1
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
End Sub
' identifies
Public Sub identifie()
Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
End Sub
' class property
Public Shared ReadOnly Property nbPersonnes() As Long
Get
Return _nbPersonnes
End Get
End Property
' instance properties
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)
' valid age?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
End Class
باستخدام البرنامج التالي:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test pg
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 لأنهما متطابقان.
![]() |
يوضح المثال التالي ذلك:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test pg
Module test
Sub Main()
' one person p1
Dim p1 As New personne("Jean", "Dupont", 30)
' p1 display
Console.Out.Write("Paramètre effectif avant modification : ")
p1.identifie()
' modification p1
modifie(p1)
' p1 display
Console.Out.Write("Paramètre effectif après modification : ")
p1.identifie()
End Sub
Sub modifie(ByVal P As personne)
' display person P
Console.Out.Write("Paramètre formel avant modification : ")
P.identifie()
' modification P
P.prenom = "Sylvie"
P.nom = "Vartan"
P.age = 52
' display 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 في الإجراء الرئيسي، وأن الكائن قد تم تعديله بالفعل بواسطة دالة التعديل.
3.1.16. مصفوفة من الأشخاص
الكائن هو جزء من البيانات مثل أي جزء آخر، وبالتالي، يمكن تجميع عدة كائنات في مصفوفة:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test pg
Module test
Sub Main()
' a table of people
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)
' display
Console.Out.WriteLine("----------------")
Dim i As Integer
For i = 0 To amis.Length - 1
amis(i).identifie()
Next i
End Sub
End Module
يُنشئ البيان Dim friends(2) As Person مصفوفة من 3 عناصر من النوع Person. يتم تهيئة هذه العناصر الثلاثة هنا بالقيمة Nothing، مما يعني أنها لا تشير إلى أي كائنات. مرة أخرى، من الناحية الفنية، نشير إلى "مصفوفة من الكائنات" عندما تكون في الواقع مجرد مصفوفة من مراجع الكائنات. لا يؤدي إنشاء مصفوفة الكائنات، التي هي كائن بحد ذاتها، إلى إنشاء أي كائنات من نوع عناصرها: يجب القيام بذلك لاحقًا. يتم الحصول على النتائج التالية:
3.2. الميراث بالتمثيل
3.2.1. نظرة عامة
نناقش هنا مفهوم التوريث. الغرض من التوريث هو "تخصيص" فئة موجودة بحيث تلبي احتياجاتنا. لنفترض أننا نريد إنشاء فئة Teacher: المعلم هو نوع معين من الأشخاص. لديهم سمات لا يمتلكها الآخرون: المادة التي يدرسونها، على سبيل المثال. لكن لديهم أيضًا سمات أي شخص: الاسم الأول، واسم العائلة، والعمر. وبالتالي، فإن المعلم هو عضو كامل العضوية في فئة "Person" ولكنه يمتلك سمات إضافية. بدلاً من كتابة فئة "Teacher" من الصفر، نفضل البناء على فئة "Person" الموجودة وتكييفها مع الخصائص المحددة للمعلمين. وهذا ما يسمح لنا مفهوم التوريث بفعله. للتعبير عن أن فئة "Teacher" ترث خصائص فئة "Person"، نكتب:
Public Class enseignant
Inherits personne
لاحظ بناء الجملة المحدد المكون من سطرين. يُطلق على فئة Person اسم الفئة الأم (أو الأساسية)، ويُطلق على فئة Teacher اسم الفئة المشتقة (أو الفرعية). يمتلك كائن Teacher جميع خصائص كائن Person: فهو يحتوي على نفس السمات والطرق. لا تتكرر هذه السمات والطرق الخاصة بالفئة الأم في تعريف الفئة الفرعية؛ بل نكتفي بتحديد السمات والطرق التي أضافتها الفئة الفرعية. نفترض أن فئة Person مُعرَّفة على النحو التالي:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' class person
Public Class personne
' class attributes
Private Shared _nbPersonnes As Long = 0
' instance attributes
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' manufacturers
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
' one more person
_nbPersonnes += 1
' construction
Me._prenom = P
Me._nom = N
Me._age = age
' follow-up
Console.Out.WriteLine("Construction personne(string, string, int)")
End Sub
Public Sub New(ByVal P As personne)
' one more person
_nbPersonnes += 1
' construction
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
' follow-up
Console.Out.WriteLine("Construction personne(string, string, int)")
End Sub
' class property
Public Shared ReadOnly Property nbPersonnes() As Long
Get
Return _nbPersonnes
End Get
End Property
' instance properties
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)
' valid age?
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
تم استبدال الطريقة Identify بخاصية Identity للقراءة فقط، والتي تحدد هوية الشخص. نقوم بإنشاء فئة Teacher التي ترث من فئة Person:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Public Class enseignant
Inherits personne
' attributes
Private _section As Integer
' manufacturer
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
' follow-up
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
' property section
Public Property section() As Integer
Get
Return _section
End Get
Set(ByVal Value As Integer)
_section = Value
End Set
End Property
End Class
تضيف فئة Teacher ما يلي إلى أساليب وسمات فئة Person:
- سمة `section`، وهي رقم القسم الذي ينتمي إليه المعلم ضمن هيئة التدريس (تقريبًا قسم واحد لكل مادة)
- منشئ جديد يقوم بتهيئة جميع سمات المعلم
الإعلان
Public Class enseignant
Inherits personne
يشير إلى أن فئة Teacher مشتقة من فئة Person.
3.2.2. إنشاء كائن Teacher
المُنشئ لفئة Teacher هو كما يلي:
' manufacturer
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
' follow-up
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) إلى فئته الأساسية، وهي في هذه الحالة فئة Person. نعلم أن هذه الفئة تحتوي على منشئ Person(string, string, int) الذي سيسمح لنا بإنشاء كائن Person باستخدام المعلمات التي تم تمريرها (P، N، age). تقوم فئة [Teacher] بتمرير المعلمات (P، N، age) إلى فئتها الأساسية على النحو التالي:
MyBase.New(P, N, age)
بمجرد إنشاء الفئة الأساسية، يستمر إنشاء كائن Teacher بتنفيذ نص المنشئ:
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
' follow-up
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
هذا مستحيل. فقد أعلنت فئة Person أن حقولها الثلاثة — _firstName و _lastName و _age — خاصة. ولا يمكن الوصول المباشر إلى هذه الحقول إلا للكائنات من نفس الفئة. أما جميع الكائنات الأخرى، بما في ذلك الكائنات الفرعية كما في هذه الحالة، فيجب أن تستخدم طرق عامة للوصول إليها. وكان الأمر سيختلف لو أن فئة Person أعلنت أن الحقول الثلاثة محمية: فقد كان ذلك سيسمح للفئات المشتقة بالوصول المباشر إلى الحقول الثلاثة. في مثالنا، كان استخدام منشئ الفئة الأصلية هو الحل الصحيح والنهج القياسي: عند إنشاء كائن فرعي، نستدعي أولاً منشئ الكائن الأصلي ثم نكمل عمليات التهيئة الخاصة بالكائن الفرعي (القسم في مثالنا).
دعونا نجمع فئتي Person و Teacher في مجموعات:
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
لاحظ أنه لترجمة الفئة الفرعية teacher، كان علينا الإشارة إلى ملف person.dll، الذي يحتوي على فئة person. دعونا نجرب برنامج اختبار أول:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test
Sub Main()
Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite)
End Sub
End Module
يقوم هذا البرنامج ببساطة بإنشاء كائن Teacher (جديد) وتحديد هويته. لا تحتوي فئة Teacher على طريقة Identity، ولكن فئتها الأم تحتوي على واحدة، وهي أيضًا عامة: من خلال الوراثة، تصبح طريقة عامة لفئة Teacher. النتائج التي تم الحصول عليها هي كما يلي:
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)
يمكننا أن نلاحظ أن:
- تم إنشاء كائن person قبل كائن teacher
- الهوية التي تم الحصول عليها هي هوية كائن الشخص
3.2.3. تحميل طريقة أو خاصية
في المثال السابق، كانت هوية الشخص جزءًا من هوية المعلم، ولكن بعض المعلومات الخاصة بفئة المعلم (القسم) مفقودة. لذلك نحتاج إلى كتابة خاصية تسمح لنا بتحديد هوية المعلم:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Public Class enseignant
Inherits personne
' attributes
Private _section As Integer
' manufacturer
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
' follow-up
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
' property section
Public Property section() As Integer
Get
Return _section
End Get
Set(ByVal Value As Integer)
_section = Value
End Set
End Property
' overload property identity
Public Shadows ReadOnly Property identite() As String
Get
Return "enseignant(" & MyBase.identite & "," & _section & ")"
End Get
End Property
End Class
تعتمد طريقة identity لفئة Teacher على طريقة identity لفئتها الأم (MyBase.identity) لعرض جزء "person" الخاص بها، ثم تكملها بحقل _section، وهو خاص بفئة Teacher. لاحظ إعلان خاصية identity:
Public Shadows ReadOnly Property identite() As String
وهو ما يشير إلى أن خاصية identity "تخفي" الطريقة التي تحمل الاسم نفسه والتي قد تكون موجودة في الفئة الأصلية. لنفترض وجود كائن Teacher E. يحتوي هذا الكائن على كائن Person بداخله:
![]() |
يتم تعريف خاصية identity في كل من فئة Teacher وفئة Person الأصلية. في فئة Teacher الفرعية، يجب أن تسبق خاصية identity كلمة shadows للإشارة إلى أننا نعيد تعريف خاصية identity جديدة لفئة Teacher.
Public Shadows ReadOnly Property identite() As String
أصبحت فئة Teacher الآن تحتوي على خاصيتين للهوية:
- الواحدة الموروثة من الفئة الأم Person
- والأخرى الخاصة بها
إذا كان E كائنًا من فئة Teacher، فإن E.identity تشير إلى طريقة identity الخاصة بفئة Teacher. نقول إن خاصية identity الخاصة بالفئة الأصلية يتم "تجاوزها" بواسطة خاصية identity الخاصة بالفئة الفرعية. بشكل عام، إذا كان O كائنًا و M طريقة، لتنفيذ الطريقة O.M، يبحث النظام عن الطريقة M بالترتيب التالي:
- في فئة الكائن O
- في فئتها الأم، إذا كان لها فئة أم
- في الفئة الأم لفئتها الأم، إن وجدت
- وما إلى ذلك…
وبالتالي، تسمح الوراثة بإعادة تعريف الطرق/الخصائص التي تحمل الاسم نفسه في الفئة الأم في الفئة الفرعية. وهذا ما يسمح بتكييف الفئة الفرعية مع احتياجاتها الخاصة. إلى جانب تعدد الأشكال، الذي سنناقشه بعد قليل، يعد تحميل الطرق/الخصائص المفرط الميزة الرئيسية للوراثة. لنأخذ نفس المثال السابق:
' options
Option Strict On
Option Explicit On
' namespaces
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)
يمكننا بسهولة كتابة
as
في الحالة الأخيرة، ستتلقى المعلمة الشكلية من النوع «person» في دالة العرض قيمة من النوع «teacher». ونظرًا لأن النوع «teacher» مشتق من النوع «person»، فإن هذا الأمر صحيح.
3.2.5. إعادة التعريف والتعدد
دعونا نكمل إجراء العرض الخاص بنا:
Sub affiche(ByVal p As personne)
' displays identity of p
Console.Out.WriteLine(p.identite)
End Sub
تُرجع طريقة p.identity سلسلة تحدد هوية كائن Person. ماذا يحدث في المثال السابق عند التعامل مع كائن Teacher:
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
لنلقِ نظرة على المثال التالي:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test
Sub Main()
' a teacher
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' a person
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' poster
Sub affiche(ByVal p As personne)
' displays identity of 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.identity نفذت خاصية الهوية لشخص في كل مرة: أولاً الشخص الموجود في المعلم e، ثم الشخص p نفسه. لم تتكيف مع الكائن الذي تم تمريره فعليًا كمعلمة لعرضه. كنا نفضل الحصول على الهوية الكاملة للمعلم e. لتحقيق ذلك، كان من الضروري أن تشير الترميز p.identity إلى خاصية الهوية للكائن الذي يشير إليه p فعليًا بدلاً من خاصية الهوية لجزء "person" من الكائن الذي يشير إليه p فعليًا. من الممكن تحقيق هذه النتيجة عن طريق إعلان identity كخاصية قابلة للتجاوز في الفئة الأساسية Person:
Public Overridable ReadOnly Property identite() As String
Get
Return "personne(" & _prenom & "," & _nom & "," & age & ")"
End Get
End Property
تجعل الكلمة الرئيسية overridable الخاصية *identite* خاصية قابلة للتحسين أو افتراضية. يمكن أيضًا تطبيق هذه الكلمة الرئيسية على الطرق. يجب على الفئات الفرعية التي تعيد تعريف خاصية أو طريقة افتراضية استخدام الكلمة الرئيسية overrides بدلاً من shadows لتأهيل الخاصية أو الطريقة المعاد تعريفها. وبالتالي، في فئة enseignant، يتم تعريف الخاصية identite على النحو التالي:
' overload property identity
Public Overrides ReadOnly Property identite() As String
Get
Return "enseignant(" & MyBase.identite & "," & _section & ")"
End Get
End Property
برنامج الاختبار:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test
Sub Main()
' a teacher
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' a person
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' poster
Sub affiche(ByVal p As personne)
' displays identity of 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 اسم الفئة التي ينتمي إليها الكائن، كما هو موضح في المثال التالي:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test2
Sub Main()
' a teacher
Console.Out.WriteLine(New enseignant("Lucile", "Dumas", 56, 61).ToString())
' a person
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 في فئتي Person و Teacher، إلا أننا ما زلنا نرى أن طريقة ToString لفئة Object لا تزال قادرة على عرض الاسم الفعلي لفئة الكائن. دعونا نتجاوز طريقة ToString في فئتي Person و Teacher:
' ToString
Public Overrides Function ToString() As String
' we return the identity property
Return identite
End Function
التعريف هو نفسه في كلا الفئتين. انظر إلى برنامج الاختبار التالي:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test
Sub Main()
' a teacher
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' a person
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' poster
Sub affiche(ByVal p As personne)
' displays identity of 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]. في المثال التالي، نستخدم هذه الفئة لتخزين قائمة بالأشخاص (بالمعنى الواسع):
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Imports System.Collections
' class listeDePersonnes
Public Class listeDePersonnes
Inherits ArrayList
' to add a person to the list
Public Overloads Sub Add(ByVal p As personne)
MyBase.Add(p)
End Sub
' an indexer
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
' another indexer
Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
Get
' we search for the person with name 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
' render (él1, él2, ..., éln)
Dim liste As String = "("
Dim i As Integer
' browse the dynamic table
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' last element
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
End Class
دعونا نقدم بعض المعلومات حول خصائص وأساليب معينة لفئة [ArrayList]:
الخاصية التي تُرجع عدد العناصر في القائمة | |
طريقة لإضافة كائن إلى القائمة | |
طريقة تعرض العنصر رقم i من القائمة |
نلاحظ أنه للحصول على العنصر i من القائمة، لم نكتب [list.Item(i)] بل كتبنا مباشرة [list(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
عندما يصادف المُترجم الترميز [list(i)]، فإنه يتحقق مما إذا كانت فئة [ArrayList] قد عرّفت خاصية بالصيغة التالية:
Default Public Property Proc(ByVal var As Integer) As Type
هنا، سيجد الإجراء [Item]. ثم سيقوم بترجمة الترميز [list(i)] إلى [list.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)
لنقم بإنشاء فئة تسمى [listOfPeople] تكون قائمة بالأشخاص، لذا فهي قائمة محددة يبدو من الطبيعي اشتقاقها من فئة [ArrayList]:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Imports System.Collections
' class listeDePersonnes
Public Class listeDePersonnes
Inherits ArrayList
' to add a person to the list
Public Shadows Sub Add(ByVal p As Object)
' p must be a person
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
' an indexer
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
' render (él1, él2, ..., éln)
Dim liste As String = "("
Dim i As Integer
' browse the dynamic table
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' last element
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
End Class
تحتوي الفئة على الطرق والخصائص التالية:
تُرجع سلسلة "تمثل" محتويات القائمة | |
طريقة لإضافة شخص إلى القائمة | |
خاصية مفهرسة افتراضية تعرض الشخص رقم i في القائمة |
دعونا ننظر إلى الميزات الجديدة:
تم إنشاء طريقة جديدة [Add].
' to add a person to the list
Public Shadows Sub Add(ByVal p As Object)
' p must be a person
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] في الفئة الفرعية مما إذا كان الكائن المضاف هو بالفعل من النوع [person] أو نوع مشتق باستخدام الدالة [TypeOf]. إذا لم يكن الأمر كذلك، يتم إلقاء استثناء باستخدام عبارة [Throw]. إذا كان الكائن المضاف هو بالفعل من النوع [person]، يتم استخدام طريقة [Add] في الفئة الأساسية لإجراء الإضافة.
يتم إنشاء خاصية مفهرسة:
' an indexer
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] تعمل مع عناصر من النوع [Person].
أخيرًا، نقوم بتجاوز طريقة [ToString] للفئة [ArrayList]:
' toString
Public Overrides Function ToString() As String
' render (él1, él2, ..., éln)
Dim liste As String = "("
Dim i As Integer
' browse the dynamic table
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' last element
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
تُرجع هذه الطريقة سلسلة نصية بالصيغة "(e1,e2,...,en)"، حيث e1 و e2 و... و en هي عناصر القائمة. لاحظ الترميز [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
تم إنشاء برنامج اختبار:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
Imports System.Collections
Module test
Sub Main()
' create an empty list of people
Dim liste As listeDePersonnes = New listeDePersonnes
' creating people
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)
' list filling
liste.Add(p1)
liste.Add(p2)
liste.Add(e1)
' list display
Console.Out.WriteLine(liste.ToString)
' add an object other than person
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 من النوع [listOfPeople]. هنا، نريد فهرسة القائمة l ليس برقم العنصر بل باسم الشخص. للقيام بذلك، نحدد خاصية مفهرسة افتراضية جديدة:
' another indexer
Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
Get
' we search for the person with name 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] بفهرسة فئة PeopleList باستخدام سلسلة N. تكون نتيجة PeopleList(N) عددًا صحيحًا. سيكون هذا العدد الصحيح هو موضع الشخص الذي يحمل الاسم N في القائمة، أو -1 إذا لم يكن هذا الشخص موجودًا في القائمة. نقوم بتعريف الخاصية get فقط، وبالتالي نمنع التعيين listOfPeople("name")=value، الذي كان سيتطلب تعريف الخاصية set. ومن هنا تأتي الكلمة الرئيسية [ReadOnly]. الكلمة الرئيسية [Shadows] ضرورية لإخفاء الخاصية الافتراضية للفئة الأساسية (على الرغم من أنها لا تحمل نفس التوقيع).
في نص طريقة get، نقوم بالتكرار عبر قائمة الأشخاص بحثًا عن الاسم N الذي تم تمريره كمعلمة. إذا وجدناه في الموضع i، فإننا نُرجع i؛ وإلا، فإننا نُرجع -1.
قد يكون برنامج الاختبار الجديد كما يلي:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' test pg
Module test
Sub Main()
' a list of people
Dim l As New listeDePersonnes
' add people
l.Add(New personne("jean", "dumornet", 10))
l.Add(New personne("pauline", "duchemin", 12))
' display
Console.Out.WriteLine(("l=" + l.ToString))
l.Add(New personne("jacques", "tartifume", 27))
Console.Out.WriteLine(("l=" + l.ToString))
' change item 1
l(1) = New personne("sylvie", "cachan", 5)
' display element 1
Console.Out.WriteLine(("l[1]=" + l(1).ToString))
' display list l
Console.Out.WriteLine(("l=" + l.ToString))
' people search
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
' attributs
...
' propriétés
...
' constructeurs
...
' méthodes
End Structure
على الرغم من أوجه التشابه في صياغتها، هناك اختلافات كبيرة بين الفئات والهياكل. على سبيل المثال، لا يوجد مفهوم الوراثة في الهياكل. إذا كنا نكتب فئة لا يُقصد منها أن تكون مشتقة من فئة أخرى، فما هي الاختلافات بين الهياكل والفئات التي ستساعدنا في الاختيار بين الاثنين؟ دعونا نستخدم المثال التالي لمعرفة ذلك:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' spersonne structure
Structure spersonne
Public nom As String
Public age As Integer
End Structure
' class cperson
Class cpersonne
Public nom As String
Public age As Integer
End Class
' a test module
Public Module test
Sub Main()
' a person p1
Dim sp1 As spersonne
sp1.nom = "paul"
sp1.age = 10
Console.Out.WriteLine(("sp1=spersonne(" & sp1.nom & "," & sp1.age & ")"))
' a p2 person
Dim sp2 As spersonne = sp1
Console.Out.WriteLine(("sp2=spersonne(" & sp2.nom & "," & sp2.age & ")"))
' sp2 is modified
sp2.nom = "nicole"
sp2.age = 30
' checking sp1 and sp2
Console.Out.WriteLine(("sp1=cpersonne(" & sp1.nom & "," & sp1.age & ")"))
Console.Out.WriteLine(("sp2=cpersonne(" & sp2.nom & "," & sp2.age & ")"))
' a cperson cp1
Dim cp1 As New cpersonne
cp1.nom = "paul"
cp1.age = 10
Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
' a P2 person
Dim cp2 As cpersonne = cp1
Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
' P2 is modified
cp2.nom = "nicole"
cp2.age = 30
' p1 and P2 verification
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)
في حين استخدمنا في الصفحات السابقة من هذا الفصل فئة Person، فإننا نستخدم الآن PersonStructure:
' structure spersonne
Structure spersonne
Public nom As String
Public age As Integer
End Structure
الإعلان
Dim sp1 As spersonne
ينشئ بنية (name, age) وقيمة 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 يتم تعديله لأنهما متطابقان. وهذا ما توضحه أيضًا نتائج البرنامج.
وبالتالي يمكننا أن نستنتج من هذه التفسيرات أن:
- قيمة المتغير من نوع البنية هي البنية نفسها
- قيمة المتغير من نوع الكائن هي عنوان الكائن الذي يشير إليه
بمجرد فهم هذا الاختلاف الأساسي، يتبين أن البنية تشبه إلى حد كبير الفئة، كما هو موضح في المثال الجديد التالي:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' structure person
Structure personne
' attributes
Private _nom As String
Private _age As Integer
' properties
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
' Manufacturer
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
' a test module
Module test
Sub Main()
' one person p1
Dim p1 As New personne("paul", 10)
Console.Out.WriteLine(("p1=" & p1.ToString))
' one person p2
Dim p2 As personne = p1
Console.Out.WriteLine(("p2=" & p2.ToString))
' p2 is modified
p2.nom = "nicole"
p2.age = 30
' checking p1 and 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
يجب تعريف الطريقتين [mean] و [standardDev] في الفئة C. انظر إلى الكود التالي:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' structure
Public Structure élève
Public _nom As String
Public _note As Double
' manufacturer
Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
Me._nom = NOM
Me._note = NOTE
End Sub
End Structure
' class notes
Public Class notes
' attribute
Protected _matière As String
Protected _élèves() As élève
' manufacturer
Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
' student & subject memorization
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
' concatenate all the notes
For i = 0 To (_élèves.Length - 1) - 1
valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "],"
Next i
'final note
If _élèves.Length <> 0 Then
valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "]"
End If
valeur += ")"
' end
Return valeur
End Function
End Class
تجمع فئة notes الدرجات الخاصة بفصل دراسي في مادة ما:
Public Class notes
' attribut
Protected _matière As String
Protected _élèves() As élève
يتم تعريف الخصائص على أنها محمية حتى يمكن الوصول إليها من فئة مشتقة. نوع الطالب هو بنية تخزن اسم الطالب ودرجته في المادة:
Public Structure élève
Public _nom As String
Public _note As Double
' manufacturer
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
' attributes
Private _moyenne As Double
Private _écartType As Double
تنفذ فئة notesStats واجهة Istats التالية:
وهذا يعني أن فئة notesStats يجب أن تحتوي على طريقتين باسم average و standardDeviation مع التوقيعات المحددة في واجهة Istats. فئة notesStats هي كما يلي:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Public Class notesStats
Inherits notes
Implements Istats
' attributes
Private _moyenne As Double
Private _écartType As Double
' manufacturer
Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
MyBase.New(MATIERE, ELEVES)
' average score calculation
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
' standard deviation
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 interface methods
Public Function moyenne() As Double Implements Istats.moyenne
' makes the average score
Return _moyenne
End Function
Public Function écartType() As Double Implements Istats.écartType
' makes the standard deviation
Return _écartType
End Function
End Class
يتم حساب المتوسط _mean والانحراف المعياري _standardDev عند إنشاء الكائن. لذلك، تقوم طريقتا mean و standardDev ببساطة بإرجاع قيم السمتين _mean و _standardDev. ترجع كلتا الطريقتين القيمة -1 إذا كان مصفوفة الطلاب فارغة.
فئة الاختبار التالية:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test
Sub Main()
' some students & notes
Dim ELEVES() As élève = {New élève("paul", 14), New élève("nicole", 16), New élève("jacques", 18)}
' recorded in a notes object
Dim anglais As New notes("anglais", ELEVES)
' and display
Console.Out.WriteLine((anglais.ToString))
' idem with mean and standard deviation
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 أن تنفذ طريقتي average و standardDev بنفسها دون تحديد أنها تنفذ واجهة Istats. ما فائدة الواجهات؟ إنها كالتالي: يمكن لدالة أن تقبل كمعلمة شكلية قيمة من النوع I. عندئذ يمكن لأي كائن من فئة C ينفذ واجهة I أن يكون معلمة فعلية لتلك الدالة. انظر المثال التالي:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' an Iexample interface
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
' a 1st class
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
'a 2nd 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
تحدد واجهة Iexample طريقتين: add و subtract. وتقوم الفئتان Class1 و Class2 بتنفيذ هذه الواجهة. لاحظ أن هاتين الفئتين لا تقومان بأي شيء آخر، وذلك لتبسيط المثال. والآن انظر المثال التالي:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' test class
Module test
'calculate
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
' the Hand function
Sub Main()
' creation of two objects class1 and class2
Dim c1 As New classe1
Dim c2 As New classe2
' static function calls calculate
calculer(4, 3, c1)
calculer(14, 13, c2)
End Sub
End Module
تقبل دالة calculate عنصرًا من النوع Iexample كمعلمة. وبالتالي، يمكنها استلام كائن من النوع class1 أو class2 لهذه المعلمة. وهذا ما يتم في الإجراء Main مع النتائج التالية:
يمكننا أن نلاحظ إذن أن هذه الخاصية تشبه التعدد الشكلي الذي ناقشناه فيما يتعلق بالفئات. فإذا كانت مجموعة من الفئات Ci غير مرتبطة ببعضها البعض عن طريق التوريث (وبالتالي لا يمكنها استخدام التعدد الشكلي القائم على التوريث) تشترك في مجموعة من الطرق ذات التوقيع نفسه، فقد يكون من المفيد تجميع هذه الطرق في واجهة I ترث منها جميع الفئات ذات الصلة. يمكن بعد ذلك استخدام مثيلات هذه الفئات Ci كمعلمات للوظائف التي تقبل معلمة من النوع I، أي الوظائف التي تستخدم فقط أساليب كائنات Ci المحددة في الواجهة I وليس السمات والأساليب المحددة للفئات Ci المختلفة. أخيرًا، لاحظ أن وراثة الواجهة يمكن أن تكون متعددة، أي يمكننا كتابة
Public Class classe
Implements I1,I2,...
حيث Ij هي واجهات.
3.6. مساحات الأسماء
لكتابة سطر على الشاشة، نستخدم العبارة
إذا نظرنا إلى تعريف فئة Console
Namespace: System
Assembly: Mscorlib (in Mscorlib.dll)
نرى أنه جزء من مساحة اسم System. وهذا يعني أنه يجب الإشارة إلى فئة Console باسم System.Console، ويجب أن نكتب في الواقع:
نتجنب ذلك باستخدام جملة الاستيراد:
نقول إننا نستورد مساحة اسم 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 يشتري هاتين المجموعتين من الفئات، حيث قامت كلتا الشركتين بتعريف فئة Person. يقوم العميل C بتجميع برنامج على النحو التالي:
إذا استخدم الملف المصدر prog.vb فئة Person، فلن يتمكن المُترجم من تحديد ما إذا كان عليه استخدام فئة Person الموجودة في ملف E1.dll أم تلك الموجودة في ملف E2.dll. وسيصدر إخطارًا بوجود خطأ. إذا حرصت الشركة E1 على إنشاء فئاتها في مساحة أسماء تسمى E1 والشركة E2 في مساحة أسماء تسمى E2، فسيتم تسمية فئتي Person بـ E1.Person و E2.Person. يجب على العميل استخدام E1.Person أو E2.Person في فئاته، وليس Person. تعمل مساحة الأسماء على حل هذا الغموض. لإنشاء فئة في مساحة أسماء، اكتب:
Namespace istia.st
Public Class personne
' définition de la classe
...
end Class
end Namespace
في هذا المثال، لنقم بإنشاء فئة Person التي درسناها سابقًا في مساحة اسم. سنختار istia.st كمساحة اسم. تصبح فئة Person كما يلي:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' creation of istia.st namespace
Namespace istia.st
Public Class personne
' attributes
Private prenom As String
Private nom As String
Private age As Integer
' method
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
' method
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
End Namespace
يتم ترجمة هذه الفئة إلى ملف person.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
الآن دعونا نستخدم فئة Person في فئة اختبار:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Imports istia.st
' pg test
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. مثال حساب الضريبة
نعيد النظر في حساب الضريبة الذي تمت تغطيته بالفعل في الفصل السابق ونعالجه باستخدام فئة. دعونا نراجع المشكلة:
نأخذ الحالة المبسطة لمكلف ضريبي لا يملك سوى راتبه ليبلغ عنه:
- نحسب عدد شرائح الضريبة للموظف على النحو التالي: nbParts = nbEnfants / 2 + 1 إذا كان غير متزوج، و nbEnfants / 2 + 2 إذا كان متزوجًا، حيث nbEnfants هو عدد الأطفال.
- إذا كان لديه ثلاثة أطفال على الأقل، فإنه يحصل على نصف حصة إضافية
- نحسب دخله الخاضع للضريبة R = 0.72 * S، حيث S هو راتبه السنوي
- نحسب معامل الأسرة QF = R / nbParts
- نحسب ضريبته I. انظر الجدول التالي:
12620.0 | 0 | 0 |
13,190 | 0.05 | 631 |
15,640 | 0.1 | 1,290.5 |
24,740 | 0.15 | 2,072.5 |
31,810 | 0.2 | 3,309.5 |
39,970 | 0.25 | 4,900 |
48,360 | 0.3 | 6,898.5 |
55,790 | 0.35 | 9,316.5 |
92,970 | 0.4 | 12,106 |
127,860 | 0.45 | 16,754.5 |
151,250 | 0.50 | 23,147.5 |
172,040 | 0.55 | 30,710 |
195,000 | 0.60 | 39,312 |
0 | 0.65 | 49,062 |
يحتوي كل صف على 3 حقول. لحساب الضريبة I، ابحث عن أول صف حيث QF <= الحقل 1. على سبيل المثال، إذا كان QF = 23,000، فسيكون الصف الذي تم العثور عليه هو
الضريبة I تساوي إذن 0.15*R - 2072.5*nbParts. إذا كان QF بحيث لا تتحقق الشرط QF<=field1 أبدًا، يتم استخدام المعاملات من الصف الأخير. هنا:
مما يعطي الضريبة I = 0.65 * R - 49062 * nbParts.
سيتم تعريف فئة **impot** على النحو التالي:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Public Class impot
' data required for tax calculation
' come from an external source
Private limites(), coeffR(), coeffN() As Decimal
' manufacturer
Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
' check that the 3 arrays have the same size
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
' it's good
Me.limites = LIMITES
Me.coeffR = COEFFR
Me.coeffN = COEFFN
End Sub
' tAX CALCULATION
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
' calculating the number of shares
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
' calculation of taxable income & family quota
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' tAX CALCULATION
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
يتم إنشاء كائن ضريبي بالبيانات اللازمة لحساب ضريبة المكلف. هذا هو الجزء الثابت من الكائن. بمجرد إنشاء هذا الكائن، يمكن استدعاء طريقة **calculate** الخاصة به بشكل متكرر لحساب ضريبة المكلف بناءً على حالته الاجتماعية (متزوج أم لا)، وعدد الأبناء، والراتب السنوي. قد يبدو برنامج الاختبار كما يلي:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Imports Microsoft.VisualBasic
Module test
Sub Main()
' interactive tax calculator
' the user enters three data points on the keyboard: married nbEnfants salary
' the program then displays the tax payable
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"
' data tables required for tax calculation
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}
' tax object creation
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
' infinite loop
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Long
While True
' tax calculation parameters are requested
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()
' anything to do?
If paramètres Is Nothing OrElse paramètres = "" Then
Exit While
End If
' check the number of arguments in the input line
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
' checking parameter validity
If Not erreur Then
' married
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
' salary
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
End If
' if the parameters are correct - the tax is calculated
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 :





