Skip to content

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،... لكن قيم خصائصها ستكون مختلفة، بحيث يكون لكل منها حالته الخاصة. وبالمثل، فإن الإعلان

    dim i, j as integer

يُنشئ كائنين (المصطلح غير صحيح هنا) من النوع (الفئة) 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

لدينا هنا تعريف لفئة، أي لنوع من أنواع البيانات. وعندما نقوم بإنشاء متغيرات من هذا النوع، فإننا نسميها كائنات أو مثيلات للفئات. وبالتالي، فإن الفئة هي قالب تُبنى منه الكائنات. يمكن أن تكون عناصر أو حقول الفئة عبارة عن بيانات (سمات)، أو أساليب (دوال)، أو خصائص. الخصائص هي أساليب خاصة تُستخدم لمعرفة أو تحديد قيمة سمات الكائن. ويمكن أن تكون هذه الحقول مصحوبة بإحدى الكلمات الرئيسية الثلاث التالية:

privé
لا يمكن الوصول إلى الحقل الخاص (private) إلا من خلال الطرق الداخلية للفئة فقط
public
يمكن الوصول إلى الحقل العام (public) من خلال أي دالة، سواء كانت مُعرَّفة داخل الفئة أم لا
protégé
لا يمكن الوصول إلى الحقل المحمي (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، فإن التعليمات التالية:

dim p1 as personne p1
p1.prenom="Jean"
p1.nom="Dupont"
p1.age=30

غير قانونية. يتعين علينا تهيئة كائن من النوع personne عبر طريقة عامة. وهذه هي وظيفة الطريقة initialise. سنكتب:

dim p1 as personne
p1.initialise("Jean","Dupont",30)

الكتابة p1.initialise صحيحة لأن initialise متاحة للجميع.

3.1.4. المشغل new

تسلسل الأوامر

dim p1 as personne
p1.initialise("Jean","Dupont",30)

غير صحيحة. الأمر

dim p1 as personne

تُعلن p1 كمرجع إلى كائن من النوع personne. هذا الكائن غير موجود بعد، وبالتالي فإن p1 لم يتم تهيئته. وهذا يشبه كتابة:

dim p1 as personne=nothing

حيث نشير صراحةً باستخدام الكلمة المفتاحية nothing إلى أن المتغير p1 لا يشير بعد إلى أي كائن. وعندما نكتب بعد ذلك

p1.initialise("Jean","Dupont",30)

فإننا نستدعي الطريقة initialise للكائن الذي يشير إليه p1. لكن هذا الكائن لا يوجد بعد، وسيُبلغ المُجمِّع عن الخطأ. لكي يشير p1 إلى كائن، يجب كتابة:

dim p1 as personne=new personne()

يؤدي ذلك إلى إنشاء كائن من النوع personne لم يتم تهيئته بعد: ستكون قيمة السمتين nom و prenom — وهما إشارات إلى كائنات من النوع String — هي nothing، وستكون قيمة age هي 0. وبالتالي، هناك تهيئة افتراضية. والآن بعد أن أصبح p1 يشير إلى كائن، فإن تعليمة تهيئة هذا الكائن

p1.initialise("Jean","Dupont",30)

صالحًا.

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 في البرنامج المستدعي:

p1.initialise("Jean","Dupont",30)

يتم استدعاء الطريقة 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
dos>personne1
Jean,Dupont,30

3.1.7. استخدام ملف الفئات المُجمَّعة (assembly)

تجدر الإشارة إلى أنه في المثال السابق، يوجد فئتان في برنامج الاختبار الخاص بنا: الفئتان personne و test1. وهناك طريقة أخرى للقيام بذلك:

  • يتم ترجمة فئة «personne» إلى ملف خاص يُسمى «تجميع» (assembly). هذا الملف له امتداد .dll

  • نقوم بتجميع الفئة test1 بالإشارة إلى التجميع الذي يحتوي على فئة personne.

يصبح الملفان المصدريان كما يلي:

test.vb

Module test1
    Sub Main()
        Dim p1 As New personne
        p1.initialise("Jean", "Dupont", 30)
        p1.identifie()
    End Sub
End module
personne2.vb

' خيارات
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 initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
        Me.prenom = P
        Me.nom = N
        Me.age = age
    End Sub    'initialise

    ' طريقة
    Public Sub identifie()
        Console.Out.WriteLine((prenom & "," & nom & "," & age))
    End Sub    'identifie
End Class 'personne

يتم تجميع الفئة 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. كان من الممكن إدراج فئات أخرى في هذا التجميع. لاستخدام عدة ملفات من الفئات المُجمَّعة أثناء التجميع، نكتب:

vbc /r:fic1.dll /r:fic2.dll ... fichierSource.vb

يؤدي تشغيل البرنامج test1.exe إلى النتائج التالية:

dos>test
Jean,Dupont,30

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

وإليكم نتائجها:

p1=Jean,Dupont,30
p2=Jean,Dupont,30

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(). وعندئذٍ يتم تهيئة سمات الكائن بقيم افتراضية. وهذا ما حدث في البرامج السابقة، حيث كتبنا:

    dim p1 as personne
    p1=new personne

لنقم بإنشاء منشئين لفئتنا 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

والنتائج التي تم الحصول عليها:

p1=Jean,Dupont,30
p2=Jean,Dupont,30

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 بواسطة

dim p1 as personne=new personne("Jean","Dupont",30)

فإن p1 يشير إلى الكائن personne("Jean","Dupont",30) ولكنه ليس الكائن نفسه. في لغة C، يمكن القول إنه مؤشر، c.a.d، وهو عنوان الكائن الذي تم إنشاؤه. إذا كتبنا بعد ذلك:

    p1=nothing

فليس الكائن personne("Jean","Dupont",30) هو الذي يتم تعديله، بل الإشارة p1 هي التي تتغير قيمتها. وسيُفقد الكائن personne("Jean","Dupont",30) إذا لم تتم الإشارة إليه بواسطة أي متغير آخر.

عند كتابة:

dim p2 as personne=p1

نقوم بتهيئة المؤشر p2: فهو «يشير» إلى نفس الكائن (يُشير إلى نفس الكائن) الذي يشير إليه المؤشر p1. وبالتالي، إذا قمنا بتعديل الكائن الذي «يشير» إليه (أو المشار إليه) المؤشر p1، فإننا نعدل الكائن المشار إليه بواسطة المؤشر p2.

عند كتابة:

dim p3 as personne =new personne(p1);

يتم إنشاء كائن جديد، وهو نسخة من الكائن المشار إليه بواسطة 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

ونحصل على النتائج التالية:

P=(Jean,Michelin,34)
P=(Jean,Michelin,56)

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

في الأمر

        Console.Out.WriteLine(("P=(" & P.prenom & "," & P.nom & "," & P.age & ")"))

نحاول الحصول على قيم الخصائص prenom و nom و age الخاصة بالشخص P. يتم عندئذٍ استدعاء طريقة get الخاصة بهذه الخصائص، والتي تُرجع قيمة السمة التي تديرها.

في الأمر

        P.age = 56

نريد تعيين قيمة الخاصية age. وعندها يتم استدعاء طريقة set لهذه الخاصية. وستتلقى القيمة 56 في معلمتها value.

يُطلق على الخاصية P لفئة C التي لا تُعرِّف سوى طريقة get اسم «للقراءة فقط». وإذا كان c كائنًا من فئة C، فسيرفض المُجمِّع العملية c.P=قيمة.

يؤدي تنفيذ برنامج الاختبار السابق إلى النتائج التالية:

P=(Jean,Michelin,34)
P=(Jean,Michelin,56)
âge (-4) invalide

وبالتالي، تتيح لنا الخصائص معالجة السمات الخاصة كما لو كانت عامة.

3.1.14. طرق الفئة وسماتها

لنفترض أننا نريد حساب عدد الكائنات [personne] التي تم إنشاؤها في أحد التطبيقات. يمكننا إدارة عداد بأنفسنا، لكننا قد ننسى الكائنات المؤقتة التي يتم إنشاؤها هنا أو هناك. يبدو أنه من الأكثر أمانًا تضمين تعليمة تزيد العداد في منشئات الفئة [personne]. تكمن المشكلة في تمرير مرجع لهذا العداد حتى تتمكن منشئة الكائنات من زيادته: لذا يجب تمرير معلمة جديدة إليها. يمكن أيضًا تضمين العداد في تعريف الفئة. وبما أنه سمة تابعة للفئة نفسها وليس لكائن معين من هذه الفئة، يتم إعلانه بطريقة مختلفة باستخدام الكلمة الرئيسية Shared:

    Private Shared _nbPersonnes As Long = 0

للإشارة إليه، نكتب personne._nbPersonnes لإظهار أنه سمة للفئة personne نفسها. هنا، أنشأنا سمة خاصة لا يمكن الوصول إليها مباشرةً من خارج الفئة. لذلك، نقوم بإنشاء خاصية عامة لإتاحة الوصول إلى سمة الفئة nbPersonnes. ولجعل قيمة nbPersonnes، لا تحتاج الطريقة get الخاصة بهذه الخاصية إلى كائن personne معين: ففي الواقع، _nbPersonnes ليس سمة لكائن معين، بل هو سمة لفئة بأكملها. ولذلك نحتاج إلى خاصية مُعلنة أيضًا باسم Shared:

    Public Shared ReadOnly Property nbPersonnes() As Long
        Get
            Return _nbPersonnes
        End Get
    End Property

والتي سيتم استدعاؤها من الخارج باستخدام الصيغة 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

نحصل على النتائج التالية:

    Nombre de personnes créées : 2

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، وهي لا تشير إلى أي كائن. مرة أخرى، وبسبب سوء استخدام المصطلحات، نُطلق مصطلح «مصفوفة كائنات» في حين أنها ليست سوى مصفوفة من مراجع الكائنات. إن إنشاء مصفوفة الكائنات، التي تُعد كائنًا بحد ذاتها، لا يؤدي إلى إنشاء أي كائن من نوع عناصرها: بل يجب القيام بذلك لاحقًا. ونحصل على النتائج التالية:

Jean,Dupont,30
Sylvie,Vartan,52
Neil,Armstrong,66

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. التعدد الشكلي

لنفترض وجود سلسلة من الفئات: C0C1C2  … Cn حيث CiCj تشير إلى أن الفئة Cj مشتقة من الفئة Ci. وينتج عن ذلك أن الفئة Cj تمتلك جميع خصائص الفئة Ci بالإضافة إلى خصائص أخرى. لنفترض أن هناك كائنات Oi من النوع Ci. من المسموح كتابة:

    Oi=Oj avec j>i

فبالواقع، وبموجب مبدأ التوريث، تمتلك الفئة Cj جميع خصائص الفئة Ci بالإضافة إلى خصائص أخرى. وبالتالي، فإن الكائن Oj من النوع Cj يحتوي في داخله على كائن من النوع Ci. العملية

Oi=Oj

تجعل Oi مرجعًا للكائن من النوع Ci الموجود داخل الكائن Oj.

إن قدرة متغير Oi من الفئة Ci على الإشارة ليس فقط إلى كائن من الفئة Ci، بل إلى أي كائن مشتق من الفئة Ci، تُسمى تعدد الأشكال: وهي قدرة المتغير على الإشارة إلى أنواع مختلفة من الكائنات. لنأخذ مثالاً وننظر إلى الدالة التالية المستقلة عن أي فئة:


    Sub affiche(ByVal p As personne)

يمكننا أيضًا كتابة

    dim p as personne
    ...
    affiche(p);

أن

    dim e as enseignant
    ...
    affiche(e);

في الحالة الأخيرة، سيتلقى المعلمة الشكلية من النوع 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. لذا عندما نكتب:

    public class personne

فإننا نكتب ضمناً:

    public class personne 
         inherits object

تُعرِّف الفئة object طريقة افتراضية باسم ToString:

Image

تُرجع الطريقة 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]:

Count
سمة تُحدد عدد عناصر القائمة
Add(Object)
طريقة تسمح بإضافة كائن إلى القائمة
Item(Integer i)
طريقة تُرجع العنصر 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

تحتوي الفئة على الطرق والخصائص التالية:

ToString
تُرجع سلسلة أحرف «تمثل» محتوى القائمة
Add(personne)
طريقة تسمح بإضافة شخص إلى القائمة
Item(Integer i)
خاصية مفهرسة افتراضيًا تعطي الشخص 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

قد نرغب في كتابة

dim p as personne=l("nom")

حيث يكون 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=personne(paul,10)
p2=personne(paul,10)
p1=personne(paul,10)
p2=personne(nicole,30)

الفرق الوحيد الملحوظ هنا بين البنية والفئة هو أنه في حالة الفئة، كان الكائنان p1 و p2 سيحصلان على نفس القيمة في نهاية البرنامج، وهي قيمة p2.

3.5. الواجهات

الواجهة هي مجموعة من النماذج الأولية للطرق أو الخصائص التي تشكل عقدًا. تلتزم الفئة التي تقرر تنفيذ واجهة ما بتوفير تنفيذ لجميع الطرق المحددة في الواجهة. ويقوم المُجمِّع بالتحقق من هذا التنفيذ. وفيما يلي، على سبيل المثال، تعريف الواجهة Istats:

Public Interface Istats
    Function moyenne() As Double
    Function écartType() As Double
End Interface

سيتم إعلان أي فئة تُنفذ هذه الواجهة على النحو التالي:

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 التالية:

Public Interface Istats
    Function moyenne() As Double
    Function écartType() As Double
End Interface

وهذا يعني أن الفئة 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 مع النتائج التالية:

17
21
127
201

ونلاحظ إذن أن لدينا هنا خاصية قريبة من تعدد الأشكال الذي رأيناه في الفئات. إذا كانت مجموعة من الفئات Ci غير مرتبطة ببعضها البعض عن طريق التوريث (وبالتالي لا يمكن استخدام تعدد الأشكال الناتج عن التوريث) تقدم مجموعة من الطرق ذات التوقيع نفسه، فقد يكون من المفيد تجميع هذه الطرق في واجهة I ترثها جميع الفئات المعنية. وبذلك يمكن استخدام مثيلات هذه الفئات Ci كمعلمات لدوال تقبل معلمة من النوع I، c.a.d. وهي دوال لا تستخدم سوى طرق الكائنات Ci المُعرَّفة في الواجهة I، ولا تستخدم السمات والطرق الخاصة بالفئات Ci المختلفة. وأخيرًا، تجدر الإشارة إلى أن وراثة الواجهات يمكن أن تكون متعددة، c.a.d. ويمكن كتابة


Public Class classe
    Implements I1,I2,...

حيث Ij هي واجهات.

3.6. مساحات الأسماء

لكتابة سطر على الشاشة، نستخدم الأمر

Console.Out.WriteLine(...)

إذا نظرنا إلى تعريف الفئة Console


Namespace: System
Assembly: Mscorlib (in Mscorlib.dll)

نكتشف أنها جزء من مساحة الأسماء System. وهذا يعني أن الفئة Console يجب أن يُشار إليها بـ System.Console، وبالتالي يجب أن نكتب في الواقع:

System.Console.Out.WriteLine(...)

يمكن تجنب ذلك باستخدام جملة imports:

imports System
...
Console.Out.WriteLine(...)

يُقال إننا نستورد مساحة الأسماء System باستخدام الجملة imports. وعندما يصادف المُجمِّع اسم فئة (في هذه الحالة Console)، فإنه سيبحث عنها في مختلف مساحات الأسماء التي تم استيرادها بواسطة الجمل imports. وهنا سيجد الفئة Console في مساحة الأسماء System. لنلاحظ الآن المعلومة الثانية المرتبطة بالفئة Console:


Assembly: Mscorlib (in Mscorlib.dll)

تشير هذه السطر إلى «التجميع» الذي يوجد فيه تعريف الفئة Console. عند إجراء التجميع خارج Visual Studio.NET، وعند الحاجة إلى تحديد مراجع مختلف ملفات dll التي تحتوي على الفئات المطلوب استخدامها، قد تكون هذه المعلومة مفيدة. تجدر الإشارة إلى أنه للإشارة إلى ملفات dll اللازمة لتجميع فئة ما، نكتب:

vbc /r:fic1.dll /r:fic2.dll ... prog.vb

عند إنشاء فئة، يمكن إنشاؤها داخل مساحة أسماء. والغرض من مساحات الأسماء هذه هو تجنب تعارض الأسماء بين الفئات عند بيعها على سبيل المثال. لنفترض وجود شركتين E1 و E2 توزعان فئات مجمعة على التوالي في dll، E1.dll و E2.dll. لنفترض أن العميل C يشتري هاتين المجموعتين من الفئات، حيث قامت كلتا الشركتين بتعريف فئة personne. يقوم العميل C بترجمة برنامج على النحو التالي:

vbc /r:E1.dll /r:E2.dll prog.vb

إذا كان المصدر 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>dir
11/03/2004  18:27               610 personne.vb
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>dir
12/03/2004  18:06             3 584 personne.dll
11/03/2004  18:27               610 personne.vb

الآن دعونا نستخدم الفئة 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 الذي يعطي النتائج التالية عند تنفيذه:

Jean,Dupont,30

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، فسنجد السطر

    24740        0.15        2072.5

ويكون الضريبة I عندئذٍ مساوية لـ 0.15*R - 2072.5*nbParts. إذا كان QF بحيث لا تتحقق العلاقة QF<=champ1 أبدًا، فسيتم استخدام معاملات السطر الأخير. وهنا:

    0                0.65        49062

مما يعطي الضريبة 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

فيما يلي مثال على تنفيذ البرنامج السابق:

dir>dir
12/03/2004  18:20             1 483 impots.vb
12/03/2004  18:21             2 805 test.vb
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 :