3. Klassen, structuren, interfaces
3.1. Het object aan de hand van het voorbeeld
3.1.1. Algemeen
We gaan nu aan de hand van een voorbeeld in op objectgeoriënteerd programmeren. Een object is een entiteit die gegevens bevat die de toestand ervan bepalen (dit noemen we eigenschappen) en functies (dit noemen we methoden). Een object wordt aangemaakt volgens een model dat we een klasse noemen:
Public Class c1
' attributen
Private p1 As type1
Private p2 As type2
....
' methode
Public Sub m1(....)
...
End Sub
' methode
Public Function m2(...)
....
End Function
End Class
Op basis van de vorige klasse C1 kunnen we talrijke objecten aanmaken, zoals O1, O2, … Ze zullen allemaal de eigenschappen p1, p2, … en de methoden m3, m4, … hebben. Maar ze zullen verschillende waarden hebben voor hun eigenschappen pi, waardoor ze elk een eigen toestand hebben. Naar analogie geldt de declaratie
twee objecten (de term is hier onjuist) van het type (klasse) Integer. Hun enige eigenschap is hun waarde. Als O1 een object is van het type C1, verwijst O1.p1 naar de eigenschap p1 van O1 en O1.m1 naar de methode m1 van O1. Laten we een eerste objectmodel bekijken: de klasse personne.
3.1.2. Definitie van de klasse ‘persoon’
De definitie van de klasse personne luidt als volgt:
Public Class personne
' attributen
Private prenom As String
Private nom As String
Private age As Integer
' methode
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
' methode
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
Dit is de definitie van een klasse, dus van een gegevenstype. Wanneer we variabelen van dit type aanmaken, noemen we ze objecten of instanties van klassen. Een klasse is dus een mal op basis waarvan objecten worden geconstrueerd. De leden of velden van een klasse kunnen gegevens (attributen), methoden (functies) of eigenschappen zijn. Eigenschappen zijn speciale methoden die dienen om de waarde van attributen van het object te achterhalen of vast te leggen. Deze velden kunnen vergezeld gaan van een van de volgende drie sleutelwoorden:
Een privéveld (private) is alleen toegankelijk via de interne methoden van de klasse | |
Een openbaar (public) veld is toegankelijk voor elke functie, ongeacht of deze al dan niet binnen de klasse is gedefinieerd | |
Een beschermd veld (protected) is alleen toegankelijk via de interne methoden van de klasse of van een afgeleid object (zie later het concept van overerving). |
Over het algemeen worden de gegevens van een klasse als privé gedeclareerd, terwijl de methoden en eigenschappen als openbaar worden gedeclareerd. Dit betekent dat de gebruiker van een object (de programmeur):
- geen directe toegang heeft tot de privégegevens van het object
- gebruik kan maken van de openbare methoden van het object, en met name van die methoden die toegang geven tot de privégegevens ervan.
De syntaxis voor het declareren van een klasse is als volgt:
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
De volgorde waarin de attributen *private, protected en public* worden gedeclareerd, maakt niet uit.
3.1.3. De methode initialiseert
Laten we teruggaan naar onze klasse [personne], die als volgt is gedeclareerd:
Public Class personne
' attributen
Private prenom As String
Private nom As String
Private age As Integer
' methode
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
' methode
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
Wat is de functie van de methode initialise? Omdat nom, prenom en age privégegevens zijn van de klasse personne, leiden de instructies:
zijn ongeldig. We moeten een object van het type personne initialiseren via een openbare methode. Dat is de taak van de methode initialise. We schrijven dan:
De notatie p1.initialise is toegestaan, omdat initialise openbaar toegankelijk is.
3.1.4. De operator new
De reeks instructies
is onjuist. De instructie
definieert p1 als een verwijzing naar een object van het type personne. Dit object bestaat nog niet en daarom is p1 niet geïnitialiseerd. Het is alsof men het volgende zou schrijven:
waarbij we met het sleutelwoord nothing expliciet aangeven dat de variabele p1 nog niet naar een object verwijst. Wanneer we vervolgens schrijven
roep je de methode initialise aan van het object waarnaar p1 verwijst. Dit object bestaat echter nog niet en de compiler zal de fout melden. Om ervoor te zorgen dat p1 naar een object verwijst, moet je het volgende schrijven:
Hierdoor wordt een nog niet geïnitialiseerd object van het type personne aangemaakt: de attributen nom en prenom, die verwijzingen zijn naar objecten van het type String, krijgen de waarde nothing, en age de waarde 0. Er vindt dus een standaardinitialisatie plaats. Nu p1 naar een object verwijst, is de initialisatie-instructie voor dit object
geldig.
3.1.5. Het sleutelwoord Me
Laten we de code van de methode initialise eens bekijken:
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
De instructie Me.prenom=P betekent dat het attribuut prenom van het huidige object (Me) de waarde P krijgt. Het sleutelwoord Me verwijst naar het huidige object: het object waarin de uitgevoerde methode zich bevindt. Hoe weten we dat? Laten we eens kijken hoe het object waarnaar p1 verwijst, wordt geïnitialiseerd in het aanroepende programma:
Er wordt een beroep gedaan op de methode initialise van het object p1. Wanneer in deze methode naar het object Me wordt verwezen, wordt in feite naar het object p1 verwezen. De methode initialise had ook als volgt geschreven kunnen worden:
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
prenom = P
nom = N
Me.age = age
End Sub
Wanneer een methode van een object verwijst naar een attribuut A van dat object, wordt de notatie Me.A impliciet gebruikt. Deze moet expliciet worden gebruikt wanneer er een conflict is tussen identificatoren. Dit is het geval bij de instructie:
Me.age=age;
waarbij age verwijst naar zowel een attribuut van het huidige object als naar de parameter age die door de methode wordt ontvangen. De dubbelzinnigheid moet dan worden opgeheven door het attribuut age aan te duiden als Me.age.
3.1.6. Een testprogramma
Hier volgt een kort testprogramma:
Public Class personne
' attributen
Private prenom As String
Private nom As String
Private age As Integer
' methode
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
' methode
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
en de verkregen resultaten:
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. Een bestand met gecompileerde klassen (assembly) gebruiken
Merk op dat er in het vorige voorbeeld twee klassen in ons testprogramma voorkomen: de klassen personne en test1. Er is nog een andere manier om dit aan te pakken:
-
we compileren de klasse ‘persoon’ in een apart bestand dat een assembly wordt genoemd. Dit bestand heeft de extensie .dll
-
we compileren de klasse test1 door te verwijzen naar de assembly die de klasse personne bevat.
De twee bronbestanden zien er dan als volgt uit:
test.vb | |
personne2.vb | |
De klasse personne wordt gecompileerd met de volgende instructie:
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
De compilatie heeft een bestand personne2.dll opgeleverd. Het is de compilatieoptie /t:library die aangeeft dat er een "assembly"-bestand moet worden gegenereerd. Laten we nu het bestand test.vb compileren:
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
De compilatieoptie /r:personne2.dll geeft de compiler aan dat hij bepaalde klassen zal vinden in het bestand personne2.dll. Wanneer de compiler in het bronbestand test.vb een verwijzing aantreft naar de klasse personne – een klasse die niet is gedeclareerd in het bronbestand test.vb – zal hij zoeken naar de klasse personne in de .dll-bestanden waarnaar wordt verwezen door de optie /r. Hier vindt hij de klasse personne in de assembly personne2.dll. Er hadden ook andere klassen in deze assembly kunnen worden opgenomen. Om bij het compileren meerdere bestanden met gecompileerde klassen te gebruiken, schrijft men:
Het uitvoeren van het programma test1.exe levert de volgende resultaten op:
3.1.8. Een andere methode initialiseert
Laten we nogmaals de klasse personne bekijken en de volgende methode eraan toevoegen:
' methode
Public Sub initialise(ByVal P As personne)
prenom = P.prenom
nom = P.nom
Me.age = P.age
End Sub
We hebben nu twee methoden met de naam initialise: dit is toegestaan zolang ze verschillende parameters accepteren. Dat is hier het geval. De parameter is nu een verwijzing P naar een persoon. De attributen van de persoon P worden vervolgens toegewezen aan het huidige object (Me). Merk op dat de methode initialise directe toegang heeft tot de attributen van het object P, hoewel deze van het type private zijn. Dit geldt altijd: een object O1 van de klasse C heeft altijd toegang tot de attributen van objecten van dezelfde klasse C. Hier volgt een test van de nieuwe klasse personne, die zoals eerder uitgelegd is gecompileerd in personne.dll:
' opties
Option Explicit On
Option Strict On
' naamruimten
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
en de resultaten daarvan:
3.1.9. Constructors van de klasse ‘persoon’
Een constructor is een procedure met de naam New die wordt aangeroepen bij het aanmaken van het object. Deze wordt doorgaans gebruikt om het object te initialiseren. Als een klasse een constructor heeft die n argumenten accepteert argi, kunnen de declaratie en initialisatie van een object van deze klasse als volgt plaatsvinden:
dim objet as classe =new classe(arg1,arg2, ... argn)
of
dim objet as classe
…
objet=new classe(arg1,arg2, ... argn)
Wanneer een klasse één of meerdere constructors heeft, moet er verplicht één van deze constructors worden gebruikt om een object van deze klasse aan te maken. Als een klasse C geen constructors heeft, beschikt deze over een standaardconstructor, namelijk de constructor zonder parameters: public New(). De attributen van het object worden dan geïnitialiseerd met standaardwaarden. Dit is wat er gebeurde in de voorgaande programma's, waar we het volgende hadden geschreven:
Laten we twee constructors maken voor onze klasse personne:
' opties
Option Explicit On
Option Strict On
' naamruimten
Imports System
' de klasse 'persoon'
Public Class personne
' attributen
Private prenom As String
Private nom As String
Private age As Integer
' constructors
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
' methoden voor het initialiseren van het object
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
' methode
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
Onze twee constructors roepen gewoon de bijbehorende methoden initialise aan. Ter herinnering: wanneer we in een constructor bijvoorbeeld de notatie initialise(P) tegenkomen, vertaalt de compiler dit naar Me.initialise(P). In de constructor wordt de methode initialise dus aangeroepen om te werken aan het object waarnaar wordt verwezen door Me, dat wil zeggen het huidige object, het object dat op dat moment wordt geconstrueerd. Hier volgt een kort testprogramma:
' opties
Option Explicit On
Option Strict On
' naamruimten
Imports System
' testpagina
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
en de verkregen resultaten:
3.1.10. Objectreferenties
We gebruiken altijd dezelfde klasse personne. Het testprogramma ziet er als volgt uit:
' opties
Option Explicit On
Option Strict On
' naamruimten
Imports System
' testpagina
Module test
Sub Main()
' p1
Dim p1 As New personne("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identifie()
' p2 verwijst naar hetzelfde object als p1
Dim p2 As personne = p1
Console.Out.Write("p2=")
p2.identifie()
' p3 verwijst naar een object dat een kopie zal zijn van het object waarnaar p1 verwijst
Dim p3 As New personne(p1)
Console.Out.Write("p3=")
p3.identifie()
' we wijzigen de status van het object waarnaar p1 verwijst
p1.initialise("Micheline", "Benoît", 67)
Console.Out.Write("p1=")
p1.identifie()
' aangezien p2 = p1, moet de toestand van het object waarnaar p2 verwijst, zijn gewijzigd
Console.Out.Write("p2=")
p2.identifie()
' aangezien p3 niet naar hetzelfde object verwijst als p1, kan het object waarnaar p3 verwijst niet van status zijn veranderd
Console.Out.Write("p3=")
p3.identifie()
End Sub
End Module
De verkregen resultaten zijn als volgt:
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
Wanneer de variabele p1 wordt gedeclareerd met
verwijst p1 naar het object personne("Jean","Dupont",30), maar is niet het object zelf. In C zouden we zeggen dat het een pointer is, c.a.d, het adres van het aangemaakte object. Als we vervolgens schrijven:
Dan wordt niet het object personne("Jean","Dupont",30) gewijzigd, maar verandert de waarde van de verwijzing p1. Het object personne("Jean","Dupont",30) gaat „verloren” als er geen enkele andere variabele naar verwijst.
Wanneer men schrijft:
wordt de pointer p2 geïnitialiseerd: deze ‘wijst’ naar hetzelfde object (verwijst naar hetzelfde object) als de pointer p1. Als we dus het object wijzigen waarnaar p1 „wijst” (of waarnaar wordt verwezen), wijzigen we ook het object waarnaar p2 verwijst.
Wanneer we schrijven:
wordt er een nieuw object aangemaakt, een kopie van het object waarnaar wordt verwezen door p1. Naar dit nieuwe object wordt verwezen door p3. Als het object waarnaar wordt verwezen door p1 wordt gewijzigd, heeft dit geen enkele invloed op het object waarnaar wordt verwezen door p3. Dit blijkt uit de verkregen resultaten.
3.1.11. Tijdelijke objecten
In een uitdrukking kan expliciet een beroep worden gedaan op de constructor van een object: dit object wordt aangemaakt, maar we hebben er geen toegang toe (om het bijvoorbeeld te wijzigen). Dit tijdelijke object wordt aangemaakt om de uitdrukking te kunnen evalueren en wordt daarna vrijgegeven. De geheugenruimte die het in beslag nam, wordt later automatisch vrijgemaakt door een programma dat ‘garbage collector’ wordt genoemd en dat tot taak heeft de geheugenruimte vrij te maken die wordt ingenomen door objecten waarnaar niet langer wordt verwezen door gegevens in het programma. Laten we het volgende nieuwe testprogramma eens bekijken:
' opties
Option Strict On
Option Explicit On
' naamruimten
Imports System
' testpagina
Module test
Sub Main()
Dim p As New personne(New personne("Jean", "Dupont", 30))
p.identifie()
End Sub
End Module
en laten we de constructors van de klasse personne aanpassen zodat ze een bericht weergeven:
' constructors
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
We krijgen de volgende resultaten:
dos>test
Constructeur personne(String, String, integer)
Constructeur personne(personne)
Jean,Dupont,30
waarin de opeenvolgende opbouw van de twee tijdelijke objecten wordt weergegeven.
3.1.12. Methoden voor het lezen en schrijven van privé-attributen
We voegen aan de klasse personne de methoden toe die nodig zijn om de status van de attributen van de objecten te lezen of te wijzigen:
Imports System
Public Class personne
' attributen
Private prenom As [String]
Private nom As [String]
Private age As Integer
' constructors
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
' identificatoren
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
We testen de nieuwe klasse met het volgende programma:
' opties
Option Strict On
Option Explicit On
' naamruimten
Imports System
' testpagina
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
en we krijgen de volgende resultaten:
3.1.13. De eigenschappen
Er is nog een andere manier om toegang te krijgen tot de attributen van een klasse, namelijk door eigenschappen aan te maken. Hiermee kunnen we privé-attributen bewerken alsof ze openbaar zijn. Laten we eens kijken naar de volgende klasse personne, waarin de eerdere accessors en modifiers zijn vervangen door eigenschappen die zowel lees- als schrijftoegang bieden:
' opties
Option Explicit On
Option Strict On
' naamruimten
Imports System
' klasse persoon
Public Class personne
' attributen
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' fabrikanten
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
' identificeert
Public Sub identifie()
Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
End Sub
' eigenschappen
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)
' geldige leeftijd?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
End Class
Met een eigenschap (Property) kun je de waarde van een attribuut opvragen (get) of instellen (set). In ons voorbeeld hebben we de namen van de attributen voorafgegaan met het teken _, zodat de eigenschappen dezelfde naam dragen als de primitieve attributen. Een eigenschap mag namelijk niet dezelfde naam hebben als het attribuut dat ze beheert, omdat er dan een naamconflict in de klasse ontstaat. We hebben onze attributen daarom _prenom, _nom en _age genoemd en de constructors en methoden dienovereenkomstig aangepast. Vervolgens hebben we drie eigenschappen aangemaakt: nom, prenom en age. Een eigenschap wordt als volgt gedeclareerd:
Public Property nom() As Type
Get
...
End Get
Set(ByVal Value As Type)
...
End Set
End Property
waarbij Type het type moet zijn van het attribuut dat door de eigenschap wordt beheerd. De eigenschap kan twee methoden hebben, namelijk get en set. De get-methode is doorgaans verantwoordelijk voor het retourneren van de waarde van het attribuut dat zij beheert (zij zou ook iets anders kunnen retourneren; niets staat dat in de weg). De methode set ontvangt een parameter genaamd **value**, die ze normaal gesproken toekent aan het attribuut dat ze beheert. Ze kan van de gelegenheid gebruikmaken om de geldigheid van de ontvangen waarde te controleren en eventueel een uitzondering te genereren als de waarde ongeldig blijkt te zijn. Dit is wat hier wordt gedaan voor de leeftijd.
Hoe worden deze get- en set-methoden aangeroepen? Laten we eens kijken naar het volgende testprogramma:
' opties
Option Explicit On
Option Strict On
' naamruimten
Imports System
' testpagina
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
In de instructie
wordt getracht de waarden van de eigenschappen prenom, nom en age van persoon P op te halen. De get-methode van deze eigenschappen wordt dan aangeroepen en retourneert de waarde van het attribuut dat ze beheren.
In de instructie
willen we de waarde van de eigenschap age vastleggen. De set-methode van deze eigenschap wordt dan aangeroepen. Deze ontvangt 56 als parameter value.
Een eigenschap P van een klasse C die alleen de get-methode definieert, wordt ‘alleen-lezen’ genoemd. Als c een object van klasse C is, wordt de bewerking c.P=waarde dan door de compiler geweigerd.
De uitvoering van het voorgaande testprogramma levert de volgende resultaten op:
Met eigenschappen kunnen we dus privé-attributen bewerken alsof ze openbaar zijn.
3.1.14. Methoden en klasse-attributen
Stel dat we het aantal [personne]-objecten willen tellen dat in een applicatie is aangemaakt. We kunnen zelf een teller bijhouden, maar dan lopen we het risico dat we tijdelijke objecten vergeten die hier en daar worden aangemaakt. Het lijkt veiliger om in de constructors van de klasse [personne] een instructie op te nemen die een teller verhoogt. Het probleem is dat er een verwijzing naar deze teller moet worden doorgegeven, zodat de constructor deze kan verhogen: er moet een nieuwe parameter aan worden doorgegeven. Men kan de teller ook opnemen in de definitie van de klasse. Aangezien het een attribuut is van de klasse zelf en niet van een specifiek object van deze klasse, wordt het anders gedeclareerd met het sleutelwoord Shared:
Om ernaar te verwijzen, schrijven we personne._nbPersonnes om aan te geven dat het een attribuut is van de klasse personne zelf. Hier hebben we een privé-attribuut aangemaakt waartoe we buiten de klasse geen directe toegang hebben. We maken daarom een openbare eigenschap aan om toegang te geven tot het klasseattribuut nbPersonnes. Om de waarde van nbPersonnes in te stellen, heeft de methode get van deze eigenschap geen specifiek personne-object nodig: _nbPersonnes is namelijk niet het attribuut van een specifiek object, maar het attribuut van een hele klasse. Daarom is er ook een eigenschap nodig die eveneens is gedeclareerd als Shared:
die van buitenaf wordt aangeroepen met de syntaxis personne.nbPersonnes. De eigenschap is als alleen-lezen gedefinieerd (ReadOnly) omdat er geen set-methode voor is. Hier volgt een voorbeeld. De klasse personne ziet er als volgt uit:
Option Explicit On
Option Strict On
' naamruimten
Imports System
' klasse
Public Class personne
' klasseattributen
Private Shared _nbPersonnes As Long = 0
' instantieattributen
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' constructors
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
' nog iemand
_nbPersonnes += 1
Me._prenom = P
Me._nom = N
Me._age = age
End Sub
Public Sub New(ByVal P As personne)
' nog een persoon
_nbPersonnes += 1
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
End Sub
' identificeert
Public Sub identifie()
Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
End Sub
' klasse-eigenschap
Public Shared ReadOnly Property nbPersonnes() As Long
Get
Return _nbPersonnes
End Get
End Property
' instantie-eigenschappen
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)
' geldige leeftijd?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
End Class
Met het volgende programma:
' opties
Option Explicit On
Option Strict On
' naamruimten
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
krijgt men de volgende resultaten:
3.1.15. Overdracht van een object aan een functie
We hebben al gezegd dat VB.NET standaard de effectieve parameters van een functie per waarde doorgeeft: de waarden van de effectieve parameters worden gekopieerd naar de formele parameters. In het geval van een object moet men zich niet laten misleiden door het systematische taalgebruik waarbij men spreekt van een object in plaats van een objectreferentie. Een object wordt uitsluitend gemanipuleerd via een verwijzing (een pointer) ernaar. Wat dus aan een functie wordt doorgegeven, is niet het object zelf, maar een verwijzing naar dat object. Het is dus de waarde van de verwijzing en niet de waarde van het object zelf die in de formele parameter wordt gekopieerd: er wordt geen nieuw object aangemaakt. Als een objectreferentie R1 aan een functie wordt doorgegeven, wordt deze gekopieerd naar de bijbehorende formele parameter R2. De verwijzingen R2 en R1 verwijzen dus naar hetzelfde object. Als de functie het object wijzigt waarnaar R2 verwijst, wijzigt zij uiteraard ook het object waarnaar R1 verwijst, aangezien het om hetzelfde object gaat.
![]() |
Dit wordt geïllustreerd in het volgende voorbeeld:
' opties
Option Explicit On
Option Strict On
' naamruimten
Imports System
' testpagina
Module test
Sub Main()
' een persoon p1
Dim p1 As New personne("Jean", "Dupont", 30)
' weergave p1
Console.Out.Write("Paramètre effectif avant modification : ")
p1.identifie()
' wijziging p1
modifie(p1)
' weergave p1
Console.Out.Write("Paramètre effectif après modification : ")
p1.identifie()
End Sub
Sub modifie(ByVal P As personne)
' weergave persoon P
Console.Out.Write("Paramètre formel avant modification : ")
P.identifie()
' wijziging P
P.prenom = "Sylvie"
P.nom = "Vartan"
P.age = 52
' P weergeven
Console.Out.Write("Paramètre formel après modification : ")
P.identifie()
End Sub
End Module
De verkregen resultaten zijn als volgt:
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
We zien dat er slechts één object is aangemaakt: dat van de persoon p1 uit de procedure Main, en dat het object inderdaad is gewijzigd door de functie modifie.
3.1.16. Een array van personen
Een object is een gegeven zoals elk ander en daarom kunnen meerdere objecten in een array worden verzameld:
' opties
Option Explicit On
Option Strict On
' naamruimten
Imports System
' testpagina
Module test
Sub Main()
' een lijst met personen
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)
' weergave
Console.Out.WriteLine("----------------")
Dim i As Integer
For i = 0 To amis.Length - 1
amis(i).identifie()
Next i
End Sub
End Module
De instructie Dim vrienden(2) As persoon creëert een array met 3 elementen van het type personne. Deze 3 elementen worden hier geïnitialiseerd met de waarden nothing en c.a.d, waarbij ze naar geen enkel object verwijzen. Ook hier wordt, in de volksmond, gesproken van een array van objecten, terwijl het slechts een array van verwijzingen naar objecten is. Het aanmaken van de array van objecten, die zelf een object is, creëert geen enkel object van het type van de elementen ervan: dat moet daarna gebeuren. We krijgen de volgende resultaten:
3.2. Overerving aan de hand van een voorbeeld
3.2.1. Algemeen
Hier bespreken we het begrip overerving. Het doel van overerving is om een bestaande klasse aan te passen zodat deze aan onze behoeften voldoet. Stel dat we een klasse enseignant willen maken: een leraar is een specifiek persoon. Hij heeft eigenschappen die een ander niet heeft: het vak dat hij onderwijst bijvoorbeeld. Maar hij heeft ook de eigenschappen die iedereen heeft: voornaam, achternaam en leeftijd. Een leraar maakt dus volledig deel uit van de klasse personne, maar heeft extra eigenschappen. In plaats van een klasse enseignant helemaal vanaf nul te schrijven, geven we er de voorkeur aan om voort te bouwen op de bestaande klasse personne en deze aan te passen aan het specifieke karakter van leraren. Het concept van overerving maakt dit mogelijk. Om aan te geven dat de klasse enseignant de eigenschappen van de klasse personne erft, schrijven we:
Public Class enseignant
Inherits personne
Let op de specifieke syntaxis over twee regels. De klasse personne wordt de bovenliggende (of moeder)klasse genoemd en de klasse enseignant de afgeleide (of dochter)klasse. Een object enseignant bezit alle eigenschappen van een object personne: het heeft dezelfde attributen en dezelfde methoden. Deze attributen en methoden van de bovenliggende klasse worden niet herhaald in de definitie van de afgeleide klasse: er worden alleen de attributen en methoden vermeld die door de afgeleide klasse zijn toegevoegd. We nemen aan dat de klasse personne als volgt is gedefinieerd:
' opties
Option Strict On
Option Explicit On
' naamruimten
Imports System
' klasse persoon
Public Class personne
' klasseattributen
Private Shared _nbPersonnes As Long = 0
' instantieattributen
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' constructors
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
' nog een persoon
_nbPersonnes += 1
' constructie
Me._prenom = P
Me._nom = N
Me._age = age
' opvolging
Console.Out.WriteLine("Construction personne(string, string, int)")
End Sub
Public Sub New(ByVal P As personne)
' nog een persoon
_nbPersonnes += 1
' bouw
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
' opvolging
Console.Out.WriteLine("Construction personne(string, string, int)")
End Sub
' klasse-eigenschap
Public Shared ReadOnly Property nbPersonnes() As Long
Get
Return _nbPersonnes
End Get
End Property
' instantie-eigenschappen
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)
' geldige leeftijd?
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
De methode identifie is vervangen door de alleen-lezen eigenschap identité, die de persoon identificeert. We maken een klasse enseignant die erft van de klasse personne:
' opties
Option Strict On
Option Explicit On
' naamruimten
Imports System
Public Class enseignant
Inherits personne
' attributen
Private _section As Integer
' constructor
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
' tracking
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
' sectie-eigenschap
Public Property section() As Integer
Get
Return _section
End Get
Set(ByVal Value As Integer)
_section = Value
End Set
End Property
End Class
De klasse enseignant voegt het volgende toe aan de methoden en attributen van de klasse personne:
- een attribuut section, dat het sectienummer is waartoe de docent behoort binnen het docentencorps (grofweg één sectie per vak)
- een nieuwe constructor waarmee alle attributen van een docent kunnen worden geïnitialiseerd
De declaratie
Public Class enseignant
Inherits personne
geeft aan dat de klasse enseignant is afgeleid van de klasse personne.
3.2.2. Een docentobject aanmaken
De constructor van de klasse enseignant is als volgt:
' constructor
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
' tracking
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
De verklaring
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
geeft aan dat de constructor vier parameters ontvangt: P, N, age, section. De constructor moet er drie, namelijk (P,N,age), doorgeven aan zijn basisklasse, in dit geval de klasse personne. We weten dat deze klasse een constructor person(string, string, int) heeft waarmee een persoon kan worden aangemaakt met de doorgegeven parameters (P,N,age). De klasse [enseignant] geeft de parameters (P, N, leeftijd) als volgt door aan haar basisklasse:
MyBase.New(P, N, age)
Zodra de constructie van de basisklasse is voltooid, wordt de constructie van het object enseignant voortgezet door de uitvoering van de body van de constructor:
Me._section = section
Kortom, de constructor van een afgeleide klasse:
- geeft aan de basisklasse de parameters door die deze nodig heeft om zichzelf te construeren
- gebruikt de overige parameters om de eigen attributen te initialiseren
Men had ook het volgende kunnen schrijven:
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
' tracking
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
Dat is onmogelijk. De klasse personne heeft haar drie velden _prenom, _nom en _age als privé (private) gedeclareerd. Alleen objecten van dezelfde klasse hebben directe toegang tot deze velden. Alle andere objecten, inclusief afgeleide objecten zoals hier, moeten via openbare methoden werken om er toegang toe te krijgen. Dit zou anders zijn geweest als de klasse personne de drie velden als beschermd (protected) had gedeclareerd: dan zou zij afgeleide klassen toestaan om directe toegang te hebben tot de drie velden. In ons voorbeeld was het gebruik van de constructor van de bovenliggende klasse dus de juiste oplossing en dit is de gebruikelijke werkwijze: bij het aanmaken van een onderliggend object roepen we eerst de constructor van het bovenliggende object aan en vullen we vervolgens de initialisaties aan die specifiek zijn voor het onderliggende object (section in ons voorbeeld).
Laten we de klassen personne en enseignant in assemblages compileren:
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
Merk op dat voor het compileren van de dochterklasse enseignant het bestand personne.dll moest worden gerefereerd, dat de klasse personne bevat. Laten we een eerste testprogramma proberen:
' opties
Option Strict On
Option Explicit On
' naamruimten
Imports System
Module test
Sub Main()
Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite)
End Sub
End Module
Dit programma beperkt zich tot het aanmaken van een object enseignant (new) en het identificeren ervan. De klasse enseignant heeft geen methode identité, maar de bovenliggende klasse heeft er wel een, die bovendien openbaar is: door overerving wordt dit een openbare methode van de klasse enseignant. De verkregen resultaten zijn als volgt:
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)
We zien dat:
- een object personne vóór het object enseignant is aangemaakt
- de verkregen identiteit is die van het object personne
3.2.3. Overbelasting van een methode of eigenschap
In het vorige voorbeeld hadden we de identiteit van het onderdeel personne van de docent, maar er ontbreekt bepaalde informatie die specifiek is voor de klasse enseignant (de sectie). We moeten dus een eigenschap schrijven waarmee de docent kan worden geïdentificeerd:
' opties
Option Strict On
Option Explicit On
' naamruimten
Imports System
Public Class enseignant
Inherits personne
' attributen
Private _section As Integer
' constructor
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
' tracking
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
' sectie-eigenschap
Public Property section() As Integer
Get
Return _section
End Get
Set(ByVal Value As Integer)
_section = Value
End Set
End Property
' overbelasting van de identiteits-eigenschap
Public Shadows ReadOnly Property identite() As String
Get
Return "enseignant(" & MyBase.identite & "," & _section & ")"
End Get
End Property
End Class
De methode identite van de klasse enseignant is gebaseerd op de methode identite van de bovenliggende klasse (MyBase.identite) om het gedeelte „personne” weer te geven en vult dit vervolgens aan met het veld _section, dat specifiek is voor de klasse enseignant. Let op de declaratie van de eigenschap identite:
Public Shadows ReadOnly Property identite() As String
wat aangeeft dat de eigenschap identite de gelijknamige methode „verbergt” die in de bovenliggende klasse zou kunnen bestaan. Stel dat we een object enseignant E hebben. Dit object bevat een object personne:
![]() |
De eigenschap 'identity' is zowel gedefinieerd in de klasse enseignant als in de bovenliggende klasse personne. In de dochterklasse enseignant moet de eigenschap identite worden voorafgegaan door het sleutelwoord **shadows om aan te geven dat er een nieuwe eigenschap identite wordt gedefinieerd voor de klasse enseignant.
Public Shadows ReadOnly Property identite() As String
De klasse enseignant beschikt nu over twee eigenschappen identite:
- de eigenschap die is overgenomen van de bovenliggende klasse personne
- een eigen eigenschap
Als E een object van het type enseignant is, verwijst E.identite naar de methode identite van de klasse enseignant. Men zegt dat de eigenschap identite van de bovenliggende klasse wordt „herdefinieerd” door de eigenschap identite van de onderliggende klasse. In het algemeen geldt: als O een object is en M een methode, dan zoekt het systeem, om de methode O.M uit te voeren, naar een methode M in de volgende volgorde:
- in de klasse van het object O
- in de bovenliggende klasse, indien deze bestaat
- in de bovenliggende klasse van de bovenliggende klasse, indien deze bestaat
- enzovoort…
Erfenis maakt het dus mogelijk om in de dochterklasse methoden/eigenschappen met dezelfde naam als in de bovenliggende klasse opnieuw te definiëren. Hierdoor kan de dochterklasse aan haar eigen behoeften worden aangepast. In combinatie met polymorfisme, dat we straks zullen bespreken, is het overschrijven van methoden/eigenschappen het belangrijkste voordeel van erfenis. Laten we hetzelfde voorbeeld als eerder bekijken:
' opties
Option Strict On
Option Explicit On
' naamruimten
Imports System
Module test
Sub Main()
Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite)
End Sub
End Module
De verkregen resultaten zijn deze keer als volgt:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Jean,Dupont,30),27)
3.2.4. Polymorfisme
Laten we eens kijken naar een reeks klassen: C0 C1 C2 … Waarbij Ci Cj aangeeft dat de klasse Cj is afgeleid van de klasse Ci. Dit houdt in dat de klasse Cj alle kenmerken van de klasse Ci heeft, plus nog enkele andere. Stel dat er objecten Oi van het type Ci zijn. Het is toegestaan om het volgende te schrijven:
Door overerving heeft de klasse Cj namelijk alle kenmerken van de klasse Ci plus nog andere. Een object Oj van het type Cj bevat dus een object van het type Ci. De bewerking
zorgt ervoor dat Oi een verwijzing is naar het object van het type Ci dat in het object Oj is opgenomen.
Het feit dat een variabele Oi van de klasse Ci in feite niet alleen kan verwijzen naar een object van de klasse Ci, maar ook naar elk object dat is afgeleid van de klasse Ci, wordt polymorfisme genoemd: het vermogen van een variabele om naar verschillende soorten objecten te verwijzen. Laten we een voorbeeld nemen en de volgende functie bekijken, die onafhankelijk is van welke klasse dan ook:
Sub affiche(ByVal p As personne)
We zouden net zo goed kunnen schrijven
als
In dit laatste geval krijgt de formele parameter van het type personne van de functie affiche een waarde van het type enseignant. Aangezien het type enseignant is afgeleid van het type personne, is dit toegestaan.
3.2.5. Herdefinitie en polymorfisme
Laten we onze procedure affiche aanvullen:
Sub affiche(ByVal p As personne)
' identiteit van p weergeven
Console.Out.WriteLine(p.identite)
End Sub
De methode p.identite retourneert een tekenreeks die het object personne. identificeert. Wat gebeurt er in het geval van ons vorige voorbeeld met een object enseignant:
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
Laten we eens naar het volgende voorbeeld kijken:
' opties
Option Strict On
Option Explicit On
' naamruimten
Imports System
Module test
Sub Main()
' een docent
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' een persoon
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' toont
Sub affiche(ByVal p As personne)
' identiteitsposter van p
Console.Out.WriteLine(p.identite)
End Sub
End Module
De verkregen resultaten zijn als volgt:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(Lucile,Dumas,56)
Construction personne(string, string, int)
personne(Jean,Dupont,30)
Uit de uitvoering blijkt dat de instructie p.identite telkens de eigenschap identite van een personne heeft uitgevoerd, de persoon in de enseignant e, en vervolgens de personne p zelf. Het heeft zich niet aangepast aan het object dat daadwerkelijk als parameter aan affiche is doorgegeven. We hadden liever de volledige identiteit van de enseignant gehad. Hiervoor had de notatie p.identite moeten verwijzen naar de eigenschap identite van het object waarnaar daadwerkelijk wordt verwezen door p, in plaats van naar de eigenschap identite van het deel "personne" van het object waarnaar p daadwerkelijk verwijst. Dit resultaat kan worden bereikt door identite te declareren als een overschrijfbare eigenschap (overridable) in de basisklasse personne:
Public Overridable ReadOnly Property identite() As String
Get
Return "personne(" & _prenom & "," & _nom & "," & age & ")"
End Get
End Property
Het sleutelwoord **overridable maakt van identite een overschrijfbare of virtuele eigenschap. Dit sleutelwoord kan ook op methoden worden toegepast. Afgeleide klassen die een virtuele eigenschap of methode herdefiniëren, moeten dan het sleutelwoord **overrides gebruiken in plaats van shadows om hun herdefinieerde eigenschap/methode aan te duiden. Zo wordt in de klasse enseignant de eigenschap identite als volgt gedefinieerd:
' identiteitsattribuut overschrijven
Public Overrides ReadOnly Property identite() As String
Get
Return "enseignant(" & MyBase.identite & "," & _section & ")"
End Get
End Property
Het testprogramma:
' opties
Option Strict On
Option Explicit On
' naamruimten
Imports System
Module test
Sub Main()
' een docent
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' een persoon
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' toont
Sub affiche(ByVal p As personne)
' identiteitskaart van p
Console.Out.WriteLine(p.identite)
End Sub
End Module
levert dan de volgende resultaten op:
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)
Deze keer hebben we de volledige identiteit van de docent gekregen. Laten we nu eens een methode definiëren in plaats van een eigenschap. De klasse object is de "bovenliggende" klasse van alle VB.NET-klassen. Dus wanneer we schrijven:
schrijven we impliciet:
De klasse object definieert een virtuele methode ToString:

De methode ToString retourneert de naam van de klasse waartoe het object behoort, zoals in het volgende voorbeeld te zien is:
' opties
Option Strict On
Option Explicit On
' naamruimten
Imports System
Module test2
Sub Main()
' een docent
Console.Out.WriteLine(New enseignant("Lucile", "Dumas", 56, 61).ToString())
' een persoon
Console.Out.WriteLine(New personne("Jean", "Dupont", 30).ToString())
End Sub
End Module
De gegenereerde resultaten zijn als volgt:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant
Construction personne(string, string, int)
personne
Opgemerkt moet worden dat, hoewel we de methode ToString niet opnieuw hebben gedefinieerd in de klassen personne en enseignant, kunnen we toch vaststellen dat de methode ToString van de klasse object toch in staat is om de werkelijke klassenaam van het object weer te geven. Laten we de methode ToString in de klassen personne en enseignant opnieuw definiëren:
' ToString
Public Overrides Function ToString() As String
' we maken de eigenschap identiteit
Return identite
End Function
De definitie is in beide klassen hetzelfde. Laten we het volgende testprogramma eens bekijken:
' opties
Option Strict On
Option Explicit On
' naamruimten
Imports System
Module test
Sub Main()
' een docent
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' een persoon
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' toont
Sub affiche(ByVal p As personne)
' identiteitskaart van p
Console.Out.WriteLine(p.identite)
End Sub
End Module
De uitvoerresultaten zijn als volgt:
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. Een indexer voor een klasse definiëren
Laten we eens kijken naar de vooraf gedefinieerde klasse [ArrayList] in het platform .NET. Met deze klasse kunnen objecten in een lijst worden opgeslagen. Ze behoort tot de naamruimte [System.Collections]. In het volgende voorbeeld gebruiken we deze klasse om een lijst met personen (in de breedste zin van het woord) op te slaan:
' opties
Option Strict On
Option Explicit On
' naamruimten
Imports System
Imports System.Collections
' klasse listeDePersonnes
Public Class listeDePersonnes
Inherits ArrayList
' om een persoon aan de lijst toe te voegen
Public Overloads Sub Add(ByVal p As personne)
MyBase.Add(p)
End Sub
' een indexeerder
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
' een andere indexeerder
Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
Get
' we zoeken de persoon met de naam 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
' geeft (element1, element2, ..., elementn) terug
Dim liste As String = "("
Dim i As Integer
' de dynamische array wordt doorlopen
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' laatste element
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
End Class
Laten we wat informatie geven over enkele eigenschappen en methoden van de klasse [ArrayList]:
attribuut dat het aantal elementen in de lijst aangeeft | |
methode waarmee een object aan de lijst kan worden toegevoegd | |
methode die het i-de element van de lijst oplevert |
We merken op dat we, om element nr. i van de lijst op te halen, niet [liste.Item(i)] hebben geschreven, maar direct [liste(i)], wat op het eerste gezicht onjuist lijkt. Dit is echter mogelijk omdat de klasse [ArrayList] een standaard-eigenschap [Item] definieert volgens een syntaxis die vergelijkbaar is met de volgende:
Default Public Property Item(ByVal i As Integer) As Object
Get
...
End Get
Set(ByVal Value As personne)
...
End Set
End Property
Wanneer de compiler de notatie [liste(i)] tegenkomt, controleert hij of de klasse [ArrayList] een eigenschap heeft gedefinieerd met de volgende handtekening:
Default Public Property Proc(ByVal var As Integer) As Type
Hier vindt hij de procedure [Item]. Vervolgens vertaalt hij de notatie [liste(i)] naar [liste.Item(i)]. De eigenschap [Item] wordt de standaard geïndexeerde eigenschap van de klasse [ArrayList] genoemd. De uitvoering van het voorgaande programma levert de volgende resultaten op:
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)
Laten we een klasse aanmaken met de naam [listeDePersonnes], die een lijst met personen zou zijn, dus een specifieke lijst die logischerwijs kan worden afgeleid van de klasse [ArrayList]:
' opties
Option Strict On
Option Explicit On
' naamruimten
Imports System
Imports System.Collections
' klasse listeDePersonnes
Public Class listeDePersonnes
Inherits ArrayList
' om een persoon aan de lijst toe te voegen
Public Shadows Sub Add(ByVal p As Object)
' moet p een persoon zijn
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
' een indexeerder
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
' geeft (element1, element2, ..., elementn) terug
Dim liste As String = "("
Dim i As Integer
' de dynamische array wordt doorlopen
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' laatste element
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
End Class
De klasse bevat de volgende methoden en eigenschappen:
geeft een tekenreeks terug die de inhoud van de lijst „weergeeft“ | |
methode waarmee een persoon aan de lijst kan worden toegevoegd | |
standaard geïndexeerde eigenschap die persoon i uit de lijst opgeeft |
Laten we eens kijken naar de nieuwe punten:
Er is een nieuwe methode [Add] aangemaakt.
' om een persoon aan de lijst toe te voegen
Public Shadows Sub Add(ByVal p As Object)
' moet p een persoon zijn
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
Er bestaat al een procedure in de bovenliggende klasse [ArrayList] met dezelfde handtekening; vandaar het trefwoord [Shadows] om aan te geven dat de nieuwe procedure die van de bovenliggende klasse vervangt. De methode [Add] van de dochterklasse controleert of het toegevoegde object inderdaad van het type [personne] is of daarvan is afgeleid met de functie [TypeOf]. Als dit niet het geval is, wordt er een uitzondering gegenereerd met de instructie [Throw]. Als het toegevoegde object wel van het type [personne] is, wordt de methode [Add] van de basisklasse gebruikt om de toevoeging uit te voeren.
Er wordt een geïndexeerde eigenschap aangemaakt:
' een 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
Er bestaat al een standaardeigenschap met de naam [Item] in de basisklasse met dezelfde handtekening. Daarom moet het sleutelwoord [Shadows] worden gebruikt om aan te geven dat de nieuwe geïndexeerde eigenschap [Index] die van de basisklasse [Item] zal "verbergen". Merk op dat dit zelfs geldt als de twee eigenschappen niet dezelfde naam hebben. Met de eigenschap [Index] kan naar persoon nr. i in de lijst worden verwezen. Deze eigenschap is gebaseerd op de eigenschap [Item] van de basisklasse om toegang te krijgen tot element nr. i van het onderliggende object [ArrayList]. Er worden typewijzigingen doorgevoerd om er rekening mee te houden dat de eigenschap [Item] werkt met elementen van het type [Object], terwijl de eigenschap [Index] werkt met elementen van het type [personne].
Ten slotte herdefiniëren we (Overrides) de methode [ToString] van de klasse [ArrayList]:
' toString
Public Overrides Function ToString() As String
' geeft (element1, element2, ..., elementn) terug
Dim liste As String = "("
Dim i As Integer
' we doorlopen de dynamische array
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' laatste element
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
Deze methode retourneert een tekenreeks in de vorm "(e1,e2,...,en)", waarbij ei de elementen van de lijst zijn. Let op de notatie [Me(i)], die verwijst naar element nr. i van het huidige object [Me]. Hierbij wordt de standaard geïndexeerde eigenschap gebruikt. [Me(i)] is dus gelijk aan [Me.Index(i)].
De klassecode wordt in het bestand [lstpersonnes2.vb] geplaatst en gecompileerd:
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
Er wordt een testprogramma opgesteld:
' opties
Option Explicit On
Option Strict On
' naamruimten
Imports System
Imports System.Collections
Module test
Sub Main()
' een lege lijst met personen aanmaken
Dim liste As listeDePersonnes = New listeDePersonnes
' personen aanmaken
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)
' de lijst vullen
liste.Add(p1)
liste.Add(p2)
liste.Add(e1)
' de lijst weergeven
Console.Out.WriteLine(liste.ToString)
' een ander object dan een persoon toevoegen
Try
liste.Add(4)
Catch e As Exception
Console.Error.WriteLine(e.Message)
End Try
End Sub
End Module
Het wordt gecompileerd:
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
en vervolgens uitgevoerd:
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
Men zou kunnen willen schrijven
waarbij l van het type [listeDePersonnes] zou zijn. Hier willen we de lijst l niet langer op een elementnummer indexeren, maar op een persoonsnaam. Hiervoor definiëren we een nieuwe, standaard geïndexeerde eigenschap:
' een andere indexeerder
Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
Get
' zoeken naar de persoon met de naam 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
De eerste regel
Default Public Shadows ReadOnly Property Index(ByVal N As String) As Integer
geeft aan dat er opnieuw een standaard geïndexeerde eigenschap wordt aangemaakt. Alle standaardeigenschappen moeten dezelfde naam hebben, in dit geval [Index]. De nieuwe eigenschap [Index] indexeert de klasse listeDePersonnes op basis van de tekenreeks N. Het resultaat van listeDePersonnes(N) is een geheel getal. Dit gehele getal is de positie in de lijst van de persoon met de naam N, of -1 als deze persoon niet in de lijst voorkomt. We definiëren alleen de eigenschap get, waardoor het schrijven van listeDePersonnes wordt verhinderd ("nom")=valeur zou de definitie van de eigenschap set hebben vereist). Vandaar het sleutelwoord [ReadOnly]. Het sleutelwoord [Shadows] is nodig om de standaard-eigenschap van de basisklasse te verbergen (hoewel deze niet dezelfde signatuur heeft).
In de body van get wordt de lijst met personen doorlopen op zoek naar de naam N die als parameter is doorgegeven. Als deze op positie i wordt gevonden, wordt i geretourneerd; anders wordt -1 geretourneerd.
Een nieuw testprogramma zou er als volgt uit kunnen zien:
' opties
Option Strict On
Option Explicit On
' naamruimten
Imports System
' testpagina
Module test
Sub Main()
' een lijst met personen
Dim l As New listeDePersonnes
' personen toevoegen
l.Add(New personne("jean", "dumornet", 10))
l.Add(New personne("pauline", "duchemin", 12))
' weergave
Console.Out.WriteLine(("l=" + l.ToString))
l.Add(New personne("jacques", "tartifume", 27))
Console.Out.WriteLine(("l=" + l.ToString))
' element 1 wijzigen
l(1) = New personne("sylvie", "cachan", 5)
' element 1 weergeven
Console.Out.WriteLine(("l[1]=" + l(1).ToString))
' lijst weergeven l
Console.Out.WriteLine(("l=" + l.ToString))
' zoeken naar personen
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
De uitvoering levert de volgende resultaten op:
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. De structuren
De structuur VB.NET is rechtstreeks afgeleid van de structuur in de programmeertaal C en lijkt sterk op een klasse. Een structuur wordt als volgt gedefinieerd:
Structure spersonne
' kenmerken
...
' eigenschappen
...
' constructors
...
' methoden
End Structure
Ondanks de gelijkenis in de syntaxis zijn er belangrijke verschillen tussen klassen en structuren. Het begrip overerving bestaat bijvoorbeeld niet bij structuren. Als we een klasse schrijven die niet afgeleid mag worden, welke verschillen tussen structuren en klassen helpen ons dan bij de keuze tussen beide? Laten we het volgende voorbeeld gebruiken om dit te ontdekken:
' opties
Option Strict On
Option Explicit On
' naamruimten
Imports System
' structuur spersonne
Structure spersonne
Public nom As String
Public age As Integer
End Structure
' klasse cpersonne
Class cpersonne
Public nom As String
Public age As Integer
End Class
' een testmodule
Public Module test
Sub Main()
' een persoon p1
Dim sp1 As spersonne
sp1.nom = "paul"
sp1.age = 10
Console.Out.WriteLine(("sp1=spersonne(" & sp1.nom & "," & sp1.age & ")"))
' een persoon p2
Dim sp2 As spersonne = sp1
Console.Out.WriteLine(("sp2=spersonne(" & sp2.nom & "," & sp2.age & ")"))
' sp2 is gewijzigd
sp2.nom = "nicole"
sp2.age = 30
' controle van sp1 en sp2
Console.Out.WriteLine(("sp1=cpersonne(" & sp1.nom & "," & sp1.age & ")"))
Console.Out.WriteLine(("sp2=cpersonne(" & sp2.nom & "," & sp2.age & ")"))
' een c-persoon cp1
Dim cp1 As New cpersonne
cp1.nom = "paul"
cp1.age = 10
Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
' een cpersoon P2
Dim cp2 As cpersonne = cp1
Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
' P2 is gewijzigd
cp2.nom = "nicole"
cp2.age = 30
' controle van P1 en P2
Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
End Sub
End Module
Als je dit programma uitvoert, krijg je de volgende resultaten:
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)
Waar op de voorgaande pagina's van dit hoofdstuk de klasse personne werd gebruikt, gebruiken we nu de structuur spersonne:
' structuur van de persoon
Structure spersonne
Public nom As String
Public age As Integer
End Structure
De verklaring
Dim sp1 As spersonne
creëert een structuur (naam, leeftijd) en de waarde van sp1 is deze structuur zelf.
De declaratie
Dim cp1 As New cpersonne
maakt een object [cpersonne] aan (grofweg het equivalent van onze structuur) en cp1 is dan het adres (de referentie) van dit object.
Laten we het samenvatten
- in het geval van de structuur is de waarde van sp1 de structuur zelf
- in het geval van de klasse is de waarde van p1 het adres van het aangemaakte object
![]() |
![]() |
Wanneer in het programma wordt geschreven
Dim sp2 As spersonne = sp1
wordt er een nieuwe structuur (naam, leeftijd) aangemaakt en geïnitialiseerd met de waarde van p1, dus de structuur zelf.
![]() |
De structuur van sp1 wordt dus gedupliceerd in sp2. Dit is een kopie van de waarde.
De instructie
Dim cp2 As cpersonne = cp1
werkt anders. De waarde van cp1 wordt gekopieerd naar cp2, maar aangezien deze waarde in feite het adres van het object is, wordt het object zelf niet gedupliceerd. Er zijn gewoon twee verwijzingen naar het object:
![]() |
In het geval van de structuur: als we de waarde van sp2 wijzigen, wijzigen we de waarde van sp1 niet, wat het programma laat zien. In het geval van het object: als het object waarnaar cp2 verwijst, wordt gewijzigd, wordt het object waarnaar cp1 verwijst ook gewijzigd, aangezien het om hetzelfde object gaat. Dit blijkt ook uit de resultaten van het programma.
Uit deze uitleg kunnen we dus concluderen dat:
- de waarde van een variabele van het type structuur de structuur zelf is
- de waarde van een variabele van het type object is het adres van het object waarnaar wordt verwezen
Zodra dit fundamentele verschil begrepen is, blijkt de structuur sterk op de klasse te lijken, zoals het volgende nieuwe voorbeeld laat zien:
' opties
Option Strict On
Option Explicit On
' naamruimten
Imports System
' persoonsstructuur
Structure personne
' attributen
Private _nom As String
Private _age As Integer
' eigenschappen
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
' Constructor
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
' een testmodule
Module test
Sub Main()
' een persoon p1
Dim p1 As New personne("paul", 10)
Console.Out.WriteLine(("p1=" & p1.ToString))
' een persoon p2
Dim p2 As personne = p1
Console.Out.WriteLine(("p2=" & p2.ToString))
' p2 is gewijzigd
p2.nom = "nicole"
p2.age = 30
' controle van p1 en p2
Console.Out.WriteLine(("p1=" & p1.ToString))
Console.Out.WriteLine(("p2=" & p2.ToString))
End Sub
End Module
We krijgen de volgende uitvoerresultaten:
Het enige opvallende verschil tussen structuur en klasse is dat bij een klasse de objecten p1 en p2 aan het einde van het programma dezelfde waarde zouden hebben gehad, namelijk die van p2.
3.5. Interfaces
Een interface is een verzameling prototypes van methoden of eigenschappen die samen een contract vormen. Een klasse die besluit een interface te implementeren, verbindt zich ertoe een implementatie te leveren van alle methoden die in de interface zijn gedefinieerd. De compiler controleert deze implementatie. Hier volgt bijvoorbeeld de definitie van de interface Istats:
Elke klasse die deze interface implementeert, wordt gedeclareerd als
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
De methoden [moyenne] en [écartType] moeten in klasse C worden gedefinieerd. Laten we de volgende code eens bekijken:
' opties
Option Strict On
Option Explicit On
' naamruimten
Imports System
' structuur
Public Structure élève
Public _nom As String
Public _note As Double
' constructor
Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
Me._nom = NOM
Me._note = NOTE
End Sub
End Structure
' klasse notes
Public Class notes
' attribuut
Protected _matière As String
Protected _élèves() As élève
' fabrikant
Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
' opslag van leerlingen en vakken
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
' alle cijfers worden samengevoegd
For i = 0 To (_élèves.Length - 1) - 1
valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "],"
Next i
'laatste cijfer
If _élèves.Length <> 0 Then
valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "]"
End If
valeur += ")"
' einde
Return valeur
End Function
End Class
De klas notes verzamelt de cijfers van een klas in een vak:
Public Class notes
' attribuut
Protected _matière As String
Protected _élèves() As élève
De attributen worden gedeclareerd als protected, zodat ze toegankelijk zijn vanuit een afgeleide klasse. Het type élève is een structuur waarin de naam van de leerling en zijn cijfer voor het vak worden opgeslagen:
Public Structure élève
Public _nom As String
Public _note As Double
' constructor
Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
Me._nom = NOM
Me._note = NOTE
End Sub
End Structure
We besluiten deze klasse notes af te leiden naar een klasse notesStats met twee extra attributen: het gemiddelde en de standaardafwijking van de cijfers:
Public Class notesStats
Inherits notes
Implements Istats
' attributen
Private _moyenne As Double
Private _écartType As Double
De klasse notesStats implementeert de volgende interface Istats:
Dit betekent dat de klasse notesStats twee methoden moet hebben met de namen moyenne en écartType, met de handtekening zoals aangegeven in de interface Istats. De klasse notesStats ziet er als volgt uit:
' opties
Option Strict On
Option Explicit On
' naamruimten
Imports System
Public Class notesStats
Inherits notes
Implements Istats
' attributen
Private _moyenne As Double
Private _écartType As Double
' constructor
Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
MyBase.New(MATIERE, ELEVES)
' berekening gemiddelde van de scores
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
' standaardafwijking
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
' methoden van de Istats-interface
Public Function moyenne() As Double Implements Istats.moyenne
' geeft het gemiddelde van de scores weer
Return _moyenne
End Function
Public Function écartType() As Double Implements Istats.écartType
' geeft de standaardafwijking weer
Return _écartType
End Function
End Class
Het gemiddelde _moyenne en de standaardafwijking _ecartType worden berekend zodra het object wordt aangemaakt. Daarom hoeven de methoden moyenne en écartType alleen de waarde van de attributen _moyenne en _ecartType terug te geven. Beide methoden geven -1 terug als de tabel met leerlingen leeg is.
De volgende testklasse:
' opties
Option Strict On
Option Explicit On
' naamruimten
Imports System
Module test
Sub Main()
' enkele leerlingen & cijfers
Dim ELEVES() As élève = {New élève("paul", 14), New élève("nicole", 16), New élève("jacques", 18)}
' die worden opgeslagen in een notenobject
Dim anglais As New notes("anglais", ELEVES)
' en die worden weergegeven
Console.Out.WriteLine((anglais.ToString))
' idem met gemiddelde en standaardafwijking
anglais = New notesStats("anglais", ELEVES)
Console.Out.WriteLine((anglais.ToString))
End Sub
End Module
levert de volgende resultaten op:
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
De klasse notesStats had de methoden moyenne en écartType heel goed voor zichzelf kunnen implementeren zonder aan te geven dat ze de interface Istats implementeerde. Wat is het nut van interfaces? Het is als volgt: een functie kan als formele parameter een waarde van het type van een interface I accepteren. Elk object van een klasse C dat de interface I implementeert, kan dan als effectieve parameter van deze functie dienen. Laten we het volgende voorbeeld bekijken:
' opties
Option Strict On
Option Explicit On
' naamruimten
Imports System
' een interface Ivoorbeeld
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
' een eerste klasse
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
'een tweede klasse
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
De interface Iexemple definieert twee methoden: ajouter en soustraire. De klassen classe1 en classe2 implementeren deze interface. Merk op dat deze klassen verder niets doen, dit om het voorbeeld eenvoudig te houden. Laten we nu het volgende voorbeeld bekijken:
' opties
Option Strict On
Option Explicit On
' naamruimten
Imports System
' testklasse
Module test
'berekenen
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
' de functie Main
Sub Main()
' aanmaken van twee objecten klasse1 en klasse2
Dim c1 As New classe1
Dim c2 As New classe2
' aanroepen van de statische functie berekenen
calculer(4, 3, c1)
calculer(14, 13, c2)
End Sub
End Module
De functie calculer accepteert als parameter een element van het type Iexemple. Deze functie kan voor deze parameter dus zowel een object van het type classe1 als van het type classe2 ontvangen. Dit wordt gedaan in de procedure Main, met de volgende resultaten:
We zien dus dat we hier te maken hebben met een eigenschap die lijkt op het polymorfisme dat we bij klassen hebben gezien. Als een verzameling klassen Ci die niet via overerving met elkaar verbonden zijn (waardoor we geen gebruik kunnen maken van het polymorfisme van overerving) een verzameling methoden met dezelfde signatuur bevat, kan het interessant zijn om deze methoden te bundelen in een interface I waarvan alle betrokken klassen zouden erven. Instanties van deze klassen Ci kunnen dan worden gebruikt als parameters van functies die een parameter van het type I toelaten, c.a.d. Dit zijn functies die uitsluitend gebruikmaken van de methoden van de Ci-objecten die in de interface I zijn gedefinieerd, en niet van de specifieke attributen en methoden van de verschillende klassen Ci. Merk ten slotte op dat er sprake kan zijn van meervoudige overerving van interfaces, c.a.d. men kan schrijven
Public Class classe
Implements I1,I2,...
waarbij de Ij interfaces zijn.
3.6. Naamruimten
Om een regel op het scherm weer te geven, gebruiken we de instructie
Als we kijken naar de definitie van de klasse Console
Namespace: System
Assembly: Mscorlib (in Mscorlib.dll)
zien we dat deze deel uitmaakt van de naamruimte System. Dit betekent dat de klasse Console aangeduid zou moeten worden met System.Console en dat we in feite het volgende zouden moeten schrijven:
Dit kan worden voorkomen door een clausule imports te gebruiken:
We zeggen dat we de naamruimte System importeren met de clausule imports. Wanneer de compiler de naam van een klasse tegenkomt (in dit geval Console), zal hij deze zoeken in de verschillende naamruimten die zijn geïmporteerd via de clausules imports. Hier vindt hij de klasse Console in de naamruimte System. Laten we nu eens kijken naar de tweede informatie die aan de klasse Console is gekoppeld:
Assembly: Mscorlib (in Mscorlib.dll)
Deze regel geeft aan in welke „assemblage“ de definitie van de klasse Console zich bevindt. Wanneer men buiten Visual Studio.NET compileert en de verwijzingen moet opgeven naar de verschillende dll-bestanden die de te gebruiken klassen bevatten, kan deze informatie nuttig zijn. Ter herinnering: om de dll te refereren die nodig zijn voor het compileren van een klasse, schrijft men:
Wanneer men een klasse aanmaakt, kan men deze binnen een naamruimte aanmaken. Het doel van deze naamruimten is om naamconflicten tussen klassen te voorkomen, bijvoorbeeld wanneer deze worden verkocht. Laten we twee bedrijven nemen, E1 en E2, die respectievelijk klassen distribueren die zijn verpakt in de dll, E1.dll en E2.dll. Stel dat een klant C deze twee sets klassen koopt, waarin beide bedrijven een klasse personne hebben gedefinieerd. Klant C compileert een programma als volgt:
Als de broncode prog.vb gebruikmaakt van de klasse personne, weet de compiler niet of hij de klasse personne uit E1.dll of die uit E2.dll moet nemen. Hij zal een foutmelding geven. Als het bedrijf E1 ervoor zorgt dat het zijn klassen aanmaakt in een naamruimte met de naam E1 en het bedrijf E2 in een naamruimte met de naam E2, zullen de twee klassen personne dan E1.personne en E2.personne heten. De klant moet in zijn klassen ofwel E1.personne, ofwel E2.personne gebruiken, maar niet personne. De naamruimte maakt het mogelijk om deze dubbelzinnigheid op te heffen. Om een klasse in een naamruimte aan te maken, schrijft men:
Namespace istia.st
Public Class personne
' definitie van de klasse
...
end Class
end Namespace
Laten we ter illustratie in een naamruimte onze eerder besproken klasse personne aanmaken. We kiezen istia.st als naamruimte. De klasse personne wordt dan:
' opties
Option Strict On
Option Explicit On
' naamruimten
Imports System
' aanmaken van de naamruimte istia.st
Namespace istia.st
Public Class personne
' attributen
Private prenom As String
Private nom As String
Private age As Integer
' methode
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
' methode
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
End Namespace
Deze klasse wordt gecompileerd tot personne.dll:
dos>vbc /t:library personne.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
Laten we nu de klasse personne in een testklasse gebruiken:
' opties
Option Strict On
Option Explicit On
' naamruimten
Imports System
Imports istia.st
' testpagina
Public Module test
Sub Main()
Dim p1 As New personne
p1.initialise("Jean", "Dupont", 30)
p1.identifie()
End Sub
End Module
Om te voorkomen dat we
Dim p1 As New istia.st.personne
hebben we de naamruimte istia.st geïmporteerd met een imports-clausule:
Imports istia.st
Laten we nu het testprogramma compileren:
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
Dit levert een bestand op met de naam test.exe, dat bij uitvoering de volgende resultaten oplevert:
3.7. Het voorbeeld IMPOTS
We nemen de belastingberekening die we in het vorige hoofdstuk al hebben bekeken en verwerken deze met behulp van een klasse. Laten we het probleem nog eens op een rijtje zetten:
We gaan uit van het vereenvoudigde geval van een belastingplichtige die alleen zijn salaris hoeft aan te geven:
- we berekenen het aantal aandelen van de werknemer nbParts=nbEnfants/2 +1 als hij ongehuwd is, nbEnfants/2+2 als hij getrouwd is, waarbij nbEnfants het aantal kinderen is.
- als hij ten minste drie kinderen heeft, krijgt hij een half deel extra
- zijn belastbaar inkomen wordt berekend als R = 0,72 * S, waarbij S zijn jaarsalaris is
- we berekenen zijn gezinscoëfficiënt QF = R / nbParts
- zijn belasting I wordt berekend. Laten we de volgende tabel bekijken:
12620,0 | 0 | 0 |
13190 | 0,05 | 631 |
15640 | 0,1 | 1290,5 |
24.740 | 0,15 | 2072,5 |
31810 | 0,2 | 3309,5 |
39.970 | 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 |
172.040 | 0,55 | 30710 |
195.000 | 0,60 | 39312 |
0 | 0,65 | 49062 |
Elke regel heeft 3 velden. Om belasting I te berekenen, zoeken we de eerste regel waar QF <= veld1. Als bijvoorbeeld QF = 23000 is, vinden we de regel
De belasting I is dan gelijk aan 0,15*R - 2072,5*nbParts. Als QF zodanig is dat de relatie QF<=veld1 nooit geldt, dan worden de coëfficiënten van de laatste regel gebruikt. In dit geval:
wat resulteert in de belasting I = 0,65 * R - 49062 * nbParts.
De klasse „impot“ wordt als volgt gedefinieerd:
' opties
Option Strict On
Option Explicit On
' naamruimten
Imports System
Public Class impot
' de gegevens die nodig zijn voor de berekening van de belasting
' zijn afkomstig van een externe bron
Private limites(), coeffR(), coeffN() As Decimal
' fabrikant
Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
' er wordt gecontroleerd of de 3 tabellen dezelfde grootte hebben
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
' alles klopt
Me.limites = LIMITES
Me.coeffR = COEFFR
Me.coeffN = COEFFN
End Sub
' berekening van de belasting
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
' berekening van het aantal aandelen
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
' berekening van het belastbaar inkomen en de gezinsquotiënt
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' berekening van de belasting
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
Er wordt een belastingobject aangemaakt met de gegevens die nodig zijn om de belasting van een belastingplichtige te berekenen. Dit is het vaste deel van het object. Zodra dit object is aangemaakt, kan de methode **calculer** herhaaldelijk worden aangeroepen om de belasting van de belastingplichtige te berekenen op basis van zijn burgerlijke staat (gehuwd of ongehuwd), het aantal kinderen en zijn jaarsalaris. Een testprogramma zou er als volgt uit kunnen zien:
' opties
Option Strict On
Option Explicit On
' naamruimten
Imports System
Imports Microsoft.VisualBasic
Module test
Sub Main()
' interactief programma voor belastingberekening
' de gebruiker voert drie gegevens in via het toetsenbord: gehuwd nbEnfants salaris
' het programma geeft vervolgens de te betalen belasting weer
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"
' tabellen met gegevens die nodig zijn voor de belastingberekening
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}
' aanmaken van een belastingobject
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
' oneindige lus
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Long
While True
' er wordt gevraagd om de parameters voor de belastingberekening
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()
' moet er iets gebeuren?
If paramètres Is Nothing OrElse paramètres = "" Then
Exit While
End If
' controle van het aantal argumenten in de ingevoerde regel
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
' controle van de geldigheid van de parameters
If Not erreur Then
' getrouwd
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
' salaris
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
End If
' als de parameters correct zijn, wordt de belasting berekend
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
Hier volgt een voorbeeld van de uitvoering van het vorige programma:
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 :





