3. Klassen, Strukturen, Schnittstellen
3.1. Objekte am Beispiel
3.1.1. Allgemeiner Überblick
Wir werden nun die objektorientierte Programmierung anhand von Beispielen erkunden. Ein Objekt ist eine Entität, die Daten enthält, die seinen Zustand definieren (sogenannte Eigenschaften), sowie Funktionen (sogenannte Methoden). Ein Objekt wird auf der Grundlage einer Vorlage erstellt, die als Klasse bezeichnet wird:
Public Class c1
' attributes
Private p1 As type1
Private p2 As type2
....
' method
Public Sub m1(....)
...
End Sub
' method
Public Function m2(...)
....
End Function
End Class
Aus der vorherigen Klasse C1 können wir viele Objekte O1, O2, … erstellen. Alle verfügen über die Eigenschaften p1, p2, … und die Methoden m3, m4, … Sie weisen jedoch unterschiedliche Werte für ihre Eigenschaften pi auf und haben somit jeweils ihren eigenen Zustand. Analog dazu lautet die Deklaration
erzeugt zwei Objekte (der Begriff ist hier ungenau) vom Typ (Klasse) Integer. Ihre einzige Eigenschaft ist ihr Wert. Wenn O1 ein Objekt vom Typ C1 ist, bezieht sich O1.p1 auf die Eigenschaft p1 von O1 und O1.m1 auf die Methode m1 von O1. Betrachten wir ein erstes Objektmodell: die Klasse Person.
3.1.2. Definition der Klasse Person
Die Definition der Klasse Person lautet wie folgt:
Public Class personne
' attributes
Private prenom As String
Private nom As String
Private age As Integer
' method
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' method
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
Hier sehen wir die Definition einer Klasse, die einen Datentyp darstellt. Wenn wir Variablen dieses Typs erstellen, bezeichnen wir sie als Objekte oder Klasseninstanzen. Eine Klasse ist somit eine Vorlage, aus der Objekte erstellt werden. Die Elemente oder Felder einer Klasse können Daten (Attribute), Methoden (Funktionen) oder Eigenschaften sein. Eigenschaften sind spezielle Methoden, die zum Abrufen oder Festlegen des Werts der Attribute eines Objekts verwendet werden. Diese Felder können mit einem der folgenden drei Schlüsselwörter versehen sein:
Auf ein privates Feld kann nur von den internen Methoden der Klasse zugegriffen werden | |
Ein öffentliches Feld ist für jede Funktion zugänglich, unabhängig davon, ob diese innerhalb der Klasse definiert ist oder nicht | |
Ein geschütztes Feld ist nur für die internen Methoden der Klasse oder für ein abgeleitetes Objekt zugänglich (siehe das Konzept der Vererbung weiter unten). |
Im Allgemeinen werden die Daten einer Klasse als privat deklariert, während ihre Methoden und Eigenschaften als öffentlich deklariert werden. Das bedeutet, dass der Benutzer eines Objekts (der Programmierer):
- keinen direkten Zugriff auf die privaten Daten des Objekts hat
- die öffentlichen Methoden des Objekts aufrufen kann, insbesondere diejenigen, die Zugriff auf dessen private Daten gewähren.
Die Syntax für die Deklaration einer Klasse lautet wie folgt:
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
Die Reihenfolge, in der private, geschützte und öffentliche Attribute deklariert werden, ist beliebig.
3.1.3. Die initialize-Methode
Kehren wir zu unserer Klasse [person] zurück, die wie folgt deklariert ist:
Public Class personne
' attributes
Private prenom As String
Private nom As String
Private age As Integer
' method
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' method
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
Welche Funktion hat die initialize-Methode? Da lastName, firstName und age private Daten der Person-Klasse sind, lauten die Anweisungen:
sind ungültig. Wir müssen ein Objekt vom Typ Person über eine öffentliche Methode initialisieren. Dies ist die Aufgabe der Methode initialize. Wir schreiben:
Die Syntax p1.initialize ist gültig, da initialize öffentlich ist.
3.1.4. Der new-Operator
Die Anweisungssequenz
ist falsch. Die Anweisung
erklärt p1 als Referenz auf ein Objekt vom Typ Person. Dieses Objekt existiert noch nicht, daher ist p1 nicht initialisiert. Es ist, als hätten wir geschrieben:
wobei wir mit dem Schlüsselwort nothing explizit angeben, dass die Variable p1 noch auf kein Objekt verweist. Wenn wir dann schreiben
, rufen wir die initialize-Methode des Objekts auf, auf das p1 verweist. Dieses Objekt existiert jedoch noch nicht, und der Compiler meldet einen Fehler. Damit p1 auf ein Objekt verweist, müssen Sie schreiben:
Dadurch wird ein nicht initialisiertes Person-Objekt erstellt: Die Attribute name und first_name, die Verweise auf String-Objekte sind, haben den Wert nothing, und age hat den Wert 0. Es findet also eine Standardinitialisierung statt. Da p1 nun auf ein Objekt verweist, wird die Initialisierungsanweisung für dieses Objekt
gültig.
3.1.5. Das Schlüsselwort Me
Sehen wir uns den Code für die Methode *Initialize* an:
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
Die Anweisung Me.firstName = P bedeutet, dass der Eigenschaft firstName des aktuellen Objekts (Me) der Wert P zugewiesen wird. Das Schlüsselwort Me bezieht sich auf das aktuelle Objekt: dasjenige, in dem die Methode ausgeführt wird. Woher wissen wir das? Schauen wir uns an, wie das von p1 referenzierte Objekt im aufrufenden Programm initialisiert wird:
Es ist die initialize-Methode des p1-Objekts, die aufgerufen wird. Wenn innerhalb dieser Methode auf das Me-Objekt verwiesen wird, bezieht es sich tatsächlich auf das p1-Objekt. Die initialize-Methode hätte auch wie folgt geschrieben werden können:
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
prenom = P
nom = N
Me.age = age
End Sub
Wenn eine Methode eines Objekts auf ein Attribut A dieses Objekts verweist, ist die Notation Me.A impliziert. Sie muss explizit verwendet werden, wenn ein Konflikt zwischen Bezeichnern vorliegt. Dies ist bei der folgenden Anweisung der Fall:
Me.age=age;
wobei age sowohl auf ein Attribut des aktuellen Objekts als auch auf den von der Methode empfangenen Parameter age verweist. Die Mehrdeutigkeit muss dann durch die Bezugnahme auf das Attribut age als Me.age aufgelöst werden.
3.1.6. Ein Testprogramm
Hier ist ein kurzes Testprogramm:
Public Class personne
' attributes
Private prenom As String
Private nom As String
Private age As Integer
' method
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' method
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
und die erhaltenen Ergebnisse:
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. Verwendung einer kompilierten Klassendatei (Assembly)
Beachten Sie, dass es im vorherigen Beispiel zwei Klassen in unserem Testprogramm gibt: die Klassen „person“ und „test1“. Es gibt noch eine andere Vorgehensweise:
-
Kompilieren Sie die „Person“-Klasse in eine separate Datei, die als Assembly bezeichnet wird. Diese Datei hat die Erweiterung .dll
-
Wir kompilieren die Klasse „test1“, indem wir auf die Assembly verweisen, die die Klasse „person“ enthält.
Die beiden Quelldateien sehen nun wie folgt aus:
test.vb | |
person2.vb | |
Die Klasse „Person“ wird durch die folgende Anweisung kompiliert:
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
Die Kompilierung erzeugte eine Datei namens personne2.dll. Es ist die Kompilierungsoption /t:library, die die Erstellung einer „Assembly“-Datei festlegt. Kompilieren wir nun die Datei 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
Die Kompilierungsoption /r:personne2.dll teilt dem Compiler mit, dass er bestimmte Klassen in der Datei personne2.dll finden wird. Wenn er in der Quelldatei test.vb einen Verweis auf die Klasse Person findet – eine Klasse, die in der Quelldatei test.vb nicht deklariert ist –, sucht er in den .dll-Dateien, auf die die Option /r verweist, nach der Klasse Person. Er findet die Klasse Person hier in der Assembly Person2.dll. Wir hätten auch andere Klassen in diese Assembly aufnehmen können. Um bei der Kompilierung mehrere kompilierte Klassendateien zu verwenden, würden wir schreiben:
Die Ausführung des Programms test1.exe liefert folgende Ergebnisse:
3.1.8. Eine weitere Methode initialisiert
Fahren wir mit der Klasse „Person“ fort und fügen wir ihr die folgende Methode hinzu:
' method
Public Sub initialise(ByVal P As personne)
prenom = P.prenom
nom = P.nom
Me.age = P.age
End Sub
Wir haben nun zwei Methoden namens Initialize: Dies ist zulässig, solange sie unterschiedliche Parameter akzeptieren. Das ist hier der Fall. Der Parameter ist nun eine Referenz P auf eine Person. Die Attribute der Person P werden dann dem aktuellen Objekt (Me) zugewiesen. Beachten Sie, dass die Initialize-Methode direkten Zugriff auf die Attribute des Objekts P hat, obwohl diese vom Typ „private“ sind. Dies gilt immer: Ein Objekt O1 der Klasse C hat immer Zugriff auf die Attribute von Objekten derselben Klasse C. Hier ist ein Test der neuen Person-Klasse, die wie zuvor erläutert in Person.dll kompiliert wurde:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
Module test1
Sub Main()
Dim p1 As New personne
p1.initialise("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identifie()
Dim p2 As New personne
p2.initialise(p1)
Console.Out.Write("p2=")
p2.identifie()
End Sub
End Module
und die Ergebnisse:
3.1.9. Konstruktoren der Klasse „Person“
Ein Konstruktor ist eine Prozedur namens New, die beim Erstellen des Objekts aufgerufen wird. Er wird im Allgemeinen zur Initialisierung des Objekts verwendet. Wenn eine Klasse einen Konstruktor hat, der n Argumente argi akzeptiert, kann die Deklaration und Initialisierung eines Objekts dieser Klasse wie folgt erfolgen:
dim objet as classe =new classe(arg1,arg2, ... argn)
oder
dim objet as classe
…
objet=new classe(arg1,arg2, ... argn)
Wenn eine Klasse einen oder mehrere Konstruktoren hat, muss einer dieser Konstruktoren verwendet werden, um ein Objekt dieser Klasse zu erstellen. Wenn eine Klasse C keine Konstruktoren hat, verfügt sie über einen Standardkonstruktor, nämlich den Konstruktor ohne Parameter: public New(). Die Attribute des Objekts werden dann mit Standardwerten initialisiert. Genau das ist in den vorherigen Programmen geschehen, in denen wir geschrieben haben:
Erstellen wir zwei Konstruktoren für unsere Klasse „Person“:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' the person class
Public Class personne
' attributes
Private prenom As String
Private nom As String
Private age As Integer
' manufacturers
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
initialise(P, N, age)
End Sub
Public Sub New(ByVal P As personne)
initialise(P)
End Sub
' object initialization methods
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
Public Sub initialise(ByVal P As personne)
prenom = P.prenom
nom = P.nom
Me.age = P.age
End Sub
' method
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
Unsere beiden Konstruktoren rufen einfach die entsprechenden initialise-Methoden auf. Beachten Sie, dass der Compiler beispielsweise die Schreibweise initialise(P) in einem Konstruktor in Me.initialise(P) umwandelt. Im Konstruktor wird die initialise-Methode daher aufgerufen, um auf das Objekt zu wirken, auf das Me verweist, also auf das aktuelle Objekt, das gerade konstruiert wird. Hier ist ein kurzes Testprogramm:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test pg
Module test
Sub Main()
Dim p1 As New personne("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identifie()
Dim p2 As New personne(p1)
Console.Out.Write("p2=")
p2.identifie()
End Sub
End Module
und die erhaltenen Ergebnisse:
3.1.10. Objektreferenzen
Wir verwenden weiterhin dieselbe Person-Klasse. Das Testprogramm sieht nun wie folgt aus:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test pg
Module test
Sub Main()
' p1
Dim p1 As New personne("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identifie()
' p2 references the same object as p1
Dim p2 As personne = p1
Console.Out.Write("p2=")
p2.identifie()
' p3 references an object that will be a copy of the object referenced by p1
Dim p3 As New personne(p1)
Console.Out.Write("p3=")
p3.identifie()
' change the state of the object referenced by p1
p1.initialise("Micheline", "Benoît", 67)
Console.Out.Write("p1=")
p1.identifie()
' as p2=p1, the object referenced by p2 must have changed state
Console.Out.Write("p2=")
p2.identifie()
' as p3 does not reference the same object as p1, the object referenced by p3 must not have changed
Console.Out.Write("p3=")
p3.identifie()
End Sub
End Module
Die erhaltenen Ergebnisse lauten wie folgt:
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
Bei der Deklaration der Variablen p1 mit
verweist p1 auf das Objekt Person("Jean", "Dupont", 30), ist aber nicht das Objekt selbst. In C würde man sagen, dass es sich um einen Zeiger handelt, d. h. um die Adresse des erstellten Objekts. Wenn wir dann schreiben:
wird nicht das Objekt person("Jean", "Dupont", 30) verändert; es ist die Referenz p1, die ihren Wert ändert. Das Objekt person("Jean", "Dupont", 30) geht „verloren“, wenn es von keiner anderen Variablen referenziert wird.
Wenn wir schreiben:
initialisieren wir den Zeiger p2: Er „zeigt“ auf dasselbe Objekt (er verweist auf dasselbe Objekt) wie der Zeiger p1. Wenn wir also das Objekt ändern, auf das p1 „zeigt“ (oder auf das er verweist), ändern wir auch das Objekt, auf das p2 verweist.
Wenn wir schreiben:
Es wird ein neues Objekt erstellt, das eine Kopie des Objekts ist, auf das p1 verweist. Auf dieses neue Objekt verweist p3. Wenn Sie das Objekt ändern, auf das p1 „zeigt“ (oder auf das es verweist), ändern Sie das Objekt, auf das p3 verweist, in keiner Weise. Das zeigen die Ergebnisse.
3.1.11. Temporäre Objekte
In einem Ausdruck können Sie den Konstruktor eines Objekts explizit aufrufen: Das Objekt wird erstellt, aber Sie können nicht darauf zugreifen (um es beispielsweise zu ändern). Dieses temporäre Objekt wird zum Auswerten des Ausdrucks erstellt und anschließend verworfen. Der von ihm belegte Speicherplatz wird später automatisch von einem Programm namens „Garbage Collector“ zurückgewonnen, dessen Aufgabe es ist, Speicherplatz zurückzugewinnen, der von Objekten belegt wird, auf die von Programmdaten nicht mehr verwiesen wird. Betrachten Sie das folgende neue Testprogramm:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' test pg
Module test
Sub Main()
Dim p As New personne(New personne("Jean", "Dupont", 30))
p.identifie()
End Sub
End Module
und ändern wir die Konstruktoren der Person-Klasse so, dass sie eine Meldung anzeigen:
' manufacturers
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
Console.Out.WriteLine("Constructeur personne(String, String, integer)")
initialise(P, N, age)
End Sub
Public Sub New(ByVal P As personne)
Console.Out.WriteLine("Constructeur personne(personne)")
initialise(P)
End Sub
Wir erhalten folgende Ergebnisse:
dos>test
Constructeur personne(String, String, integer)
Constructeur personne(personne)
Jean,Dupont,30
die schrittweise Erstellung der beiden temporären Objekte zeigt.
3.1.12. Methoden zum Lesen und Schreiben privater Attribute
Wir fügen der Klasse „Person“ die erforderlichen Methoden hinzu, um den Zustand der Attribute der Objekte zu lesen oder zu ändern:
Imports System
Public Class personne
' attributes
Private prenom As [String]
Private nom As [String]
Private age As Integer
' manufacturers
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
Public Sub New(ByVal P As personne)
Me.prenom = P.prenom
Me.nom = P.nom
Me.age = P.age
End Sub
' identifies
Public Sub identifie()
Console.Out.WriteLine((prenom + "," + nom + "," + age))
End Sub
' accessors
Public Function getPrenom() As [String]
Return prenom
End Function
Public Function getNom() As [String]
Return nom
End Function
Public Function getAge() As Integer
Return age
End Function
'modifiers
Public Sub setPrenom(ByVal P As [String])
Me.prenom = P
End Sub
Public Sub setNom(ByVal N As [String])
Me.nom = N
End Sub
Public Sub setAge(ByVal age As Integer)
Me.age = age
End Sub
End Class
Wir testen die neue Klasse mit dem folgenden Programm:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' test pg
Public Module test
Sub Main()
Dim P As New personne("Jean", "Michelin", 34)
Console.Out.WriteLine(("P=(" & P.getPrenom() & "," & P.getNom() & "," & P.getAge() & ")"))
P.setAge(56)
Console.Out.WriteLine(("P=(" & P.getPrenom() & "," & P.getNom() & "," & P.getAge() & ")"))
End Sub
End Module
und wir erhalten folgende Ergebnisse:
3.1.13. Eigenschaften
Es gibt noch eine weitere Möglichkeit, auf die Attribute einer Klasse zuzugreifen: durch das Erstellen von Eigenschaften. Diese ermöglichen es uns, private Attribute so zu manipulieren, als wären sie öffentlich. Betrachten Sie die folgende Person-Klasse, in der die vorherigen Getter und Setter durch Lese-Schreib-Eigenschaften ersetzt wurden:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' class person
Public Class personne
' attributes
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' manufacturers
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
Me._prenom = P
Me._nom = N
Me._age = age
End Sub
Public Sub New(ByVal P As personne)
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
End Sub
' identifies
Public Sub identifie()
Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
End Sub
' properties
Public Property prenom() As String
Get
Return _prenom
End Get
Set(ByVal Value As String)
_prenom = Value
End Set
End Property
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = Value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
' valid age?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
End Class
Eine Eigenschaft ermöglicht es Ihnen, den Wert eines Attributs zu lesen (get) oder festzulegen (set). In unserem Beispiel haben wir den Attributnamen ein Unterstrich (_) vorangestellt, damit die Eigenschaften dieselben Namen wie die primitiven Attribute haben. Der Grund dafür ist, dass eine Eigenschaft nicht denselben Namen wie das von ihr verwaltete Attribut haben darf; andernfalls käme es zu einem Namenskonflikt innerhalb der Klasse. Wir haben unsere Attribute daher _firstName, _lastName und _age genannt und die Konstruktoren und Methoden entsprechend angepasst. Anschließend haben wir drei Eigenschaften erstellt: lastName, firstName und age. Eine Eigenschaft wird wie folgt deklariert:
Public Property nom() As Type
Get
...
End Get
Set(ByVal Value As Type)
...
End Set
End Property
wobei Type der Typ des von der Eigenschaft verwalteten Attributs sein muss. Sie kann zwei Methoden namens get und set haben. Die get-Methode ist in der Regel dafür zuständig, den Wert des von ihr verwalteten Attributs zurückzugeben (sie könnte auch etwas anderes zurückgeben; nichts hindert sie daran). Die „set“-Methode erhält einen Parameter namens „value“, den sie normalerweise dem von ihr verwalteten Attribut zuweist. Sie kann diese Gelegenheit nutzen, um die Gültigkeit des empfangenen Werts zu überprüfen und gegebenenfalls eine Ausnahme auszulösen, wenn der Wert ungültig ist. Genau das geschieht hier für das Alter.
Wie werden diese get- und set-Methoden aufgerufen? Betrachten Sie das folgende Testprogramm:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test pg
Module test
Sub Main()
Dim P As New personne("Jean", "Michelin", 34)
Console.Out.WriteLine(("P=(" & P.prenom & "," & P.nom & "," & P.age & ")"))
P.age = 56
Console.Out.WriteLine(("P=(" & P.prenom & "," & P.nom & "," & P.age & ")"))
Try
P.age = -4
Catch ex As Exception
Console.Error.WriteLine(ex.Message)
End Try
End Sub
End Module
In der Anweisung
versuchen wir, die Werte der Eigenschaften first_name, last_name und age der Person P abzurufen. Es ist die get-Methode dieser Eigenschaften, die dann aufgerufen wird und den Wert des von ihr verwalteten Attributs zurückgibt.
In der Anweisung
wollen wir den Wert der Eigenschaft „age“ festlegen. Die Set-Methode dieser Eigenschaft wird dann aufgerufen. Sie erhält den Wert 56 als Wertparameter.
Eine Eigenschaft P einer Klasse C, die nur die get-Methode definiert, wird als schreibgeschützt bezeichnet. Wenn c ein Objekt der Klasse C ist, wird die Operation c.P=value vom Compiler abgelehnt.
Die Ausführung des vorangegangenen Testprogramms liefert folgende Ergebnisse:
Eigenschaften ermöglichen es uns daher, private Attribute so zu manipulieren, als wären sie öffentlich.
3.1.14. Klassenmethoden und -attribute
Angenommen, wir möchten die Anzahl der in einer Anwendung erstellten [person]-Objekte zählen. Wir könnten selbst einen Zähler verwalten, aber dabei besteht die Gefahr, dass wir temporäre Objekte vergessen, die hier und da erstellt werden. Es erscheint sicherer, in die Konstruktoren der [person]-Klasse eine Anweisung einzufügen, die einen Zähler inkrementiert. Das Problem besteht darin, eine Referenz auf diesen Zähler zu übergeben, damit der Konstruktor ihn inkrementieren kann: Wir müssen ihnen einen neuen Parameter übergeben. Wir können den Zähler auch in die Klassendefinition aufnehmen. Da es sich um ein Attribut der Klasse selbst und nicht um ein bestimmtes Objekt dieser Klasse handelt, deklarieren wir es anders, indem wir das Schlüsselwort „Shared“ verwenden:
Um darauf zu verweisen, schreiben wir person.\_nbPeople, um zu verdeutlichen, dass es sich um ein Attribut der Klasse Person* selbst handelt. Hier haben wir ein privates Attribut erstellt, auf das von außerhalb der Klasse nicht direkt zugegriffen werden kann. Wir erstellen daher eine öffentliche Eigenschaft, um Zugriff auf das Klassenattribut nbPeople zu gewähren. Um den Wert von nbPeople zurückzugeben, benötigt die Get-Methode dieser Eigenschaft kein spezifisches Person-Objekt: Tatsächlich ist _nbPeople nicht das Attribut eines bestimmten Objekts, sondern das Attribut der gesamten Klasse. Daher benötigen wir eine Eigenschaft, die ebenfalls als Shared* deklariert ist:
die von außen mit der Syntax person.nbPeople aufgerufen wird. Die Eigenschaft wird als schreibgeschützt (ReadOnly) deklariert, da sie keine Set-Methode bereitstellt. Hier ist ein Beispiel. Die Person-Klasse sieht nun wie folgt aus:
Option Explicit On
Option Strict On
' namespaces
Imports System
' class
Public Class personne
' class attributes
Private Shared _nbPersonnes As Long = 0
' instance attributes
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' manufacturers
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
' one more person
_nbPersonnes += 1
Me._prenom = P
Me._nom = N
Me._age = age
End Sub
Public Sub New(ByVal P As personne)
' one more person
_nbPersonnes += 1
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
End Sub
' identifies
Public Sub identifie()
Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
End Sub
' class property
Public Shared ReadOnly Property nbPersonnes() As Long
Get
Return _nbPersonnes
End Get
End Property
' instance properties
Public Property prenom() As String
Get
Return _prenom
End Get
Set(ByVal Value As String)
_prenom = Value
End Set
End Property
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = Value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
' valid age?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
End Class
Mit dem folgenden Programm:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test pg
Module test
Sub Main()
Dim p1 As New personne("Jean", "Dupont", 30)
Dim p2 As New personne(p1)
Console.Out.WriteLine(("Nombre de personnes créées : " & personne.nbPersonnes))
End Sub
End Module
Es werden folgende Ergebnisse erhalten:
3.1.15. Übergabe eines Objekts an eine Funktion
Wir haben bereits erwähnt, dass VB.NET standardmäßig tatsächliche Funktionsparameter als Wert übergibt: Die Werte der tatsächlichen Parameter werden in die formalen Parameter kopiert. Lassen Sie sich beim Umgang mit einem Objekt nicht von der gängigen sprachlichen Konvention täuschen, von einem „Objekt“ statt von einer „Objektreferenz“ zu sprechen. Ein Objekt wird ausschließlich über eine Referenz (einen Zeiger) darauf manipuliert. An eine Funktion übergeben wird daher nicht das Objekt selbst, sondern eine Referenz auf dieses Objekt. Es ist somit der Wert der Referenz – und nicht der Wert des Objekts selbst –, der in den formalen Parameter kopiert wird: Es wird kein neues Objekt erstellt. Wird eine Objektreferenz R1 an eine Funktion übergeben, wird sie in den entsprechenden formalen Parameter R2 kopiert. Somit verweisen die Referenzen R2 und R1 auf dasselbe Objekt. Wenn die Funktion das von R2 referenzierte Objekt verändert, verändert sie offensichtlich auch das von R1 referenzierte, da es sich um dasselbe Objekt handelt.
![]() |
Dies wird durch das folgende Beispiel veranschaulicht:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test pg
Module test
Sub Main()
' one person p1
Dim p1 As New personne("Jean", "Dupont", 30)
' p1 display
Console.Out.Write("Paramètre effectif avant modification : ")
p1.identifie()
' modification p1
modifie(p1)
' p1 display
Console.Out.Write("Paramètre effectif après modification : ")
p1.identifie()
End Sub
Sub modifie(ByVal P As personne)
' display person P
Console.Out.Write("Paramètre formel avant modification : ")
P.identifie()
' modification P
P.prenom = "Sylvie"
P.nom = "Vartan"
P.age = 52
' display P
Console.Out.Write("Paramètre formel après modification : ")
P.identifie()
End Sub
End Module
Die erhaltenen Ergebnisse lauten wie folgt:
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
Wir sehen, dass nur ein Objekt erstellt wird: das der Person p1 in der Main-Prozedur, und dass das Objekt tatsächlich durch die modify-Funktion geändert wurde.
3.1.16. Ein Array von Personen
Ein Objekt ist ein Datenelement wie jedes andere, und als solches können mehrere Objekte in einem Array zusammengefasst werden:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test pg
Module test
Sub Main()
' a table of people
Dim amis(2) As personne
amis(0) = New personne("Jean", "Dupont", 30)
amis(1) = New personne("Sylvie", "Vartan", 52)
amis(2) = New personne("Neil", "Armstrong", 66)
' display
Console.Out.WriteLine("----------------")
Dim i As Integer
For i = 0 To amis.Length - 1
amis(i).identifie()
Next i
End Sub
End Module
Die Anweisung Dim friends(2) As Person erstellt ein Array mit 3 Elementen vom Typ Person. Diese 3 Elemente werden hier mit dem Wert Nothing initialisiert, was bedeutet, dass sie auf keine Objekte verweisen. Auch hier sprechen wir im technischen Sinne von einem „Array von Objekten“, obwohl es sich eigentlich nur um ein Array von Objektreferenzen handelt. Das Erstellen des Objekt-Arrays, das selbst ein Objekt ist, erzeugt keine Objekte vom Typ seiner Elemente: Dies muss später erfolgen. Es werden folgende Ergebnisse erzielt:
3.2. Vererbung durch Beispiel
3.2.1. Übersicht
Hier besprechen wir das Konzept der Vererbung. Der Zweck der Vererbung besteht darin, eine bestehende Klasse so „anzupassen“, dass sie unseren Anforderungen entspricht. Angenommen, wir möchten eine Klasse „Lehrer“ erstellen: Ein Lehrer ist eine bestimmte Art von Person. Er verfügt über Attribute, die andere Personen nicht haben, beispielsweise das Fach, das er unterrichtet. Er hat aber auch die Attribute einer beliebigen Person: Vorname, Nachname und Alter. Ein Lehrer ist daher ein vollwertiges Mitglied der Klasse „Person“, verfügt jedoch über zusätzliche Attribute. Anstatt eine Klasse „Lehrer“ von Grund auf neu zu schreiben, würden wir es vorziehen, auf der bestehenden Klasse „Person“ aufzubauen und diese an die spezifischen Eigenschaften von Lehrern anzupassen. Genau das ermöglicht uns das Konzept der Vererbung. Um auszudrücken, dass die Klasse „Lehrer“ die Eigenschaften der Klasse „Person“ erbt, schreiben wir:
Public Class enseignant
Inherits personne
Beachten Sie die spezielle zweizeilige Syntax. Die Klasse „Person“ wird als übergeordnete (oder Basis-)Klasse bezeichnet, die Klasse „Lehrer“ als abgeleitete (oder untergeordnete) Klasse. Ein „Lehrer“-Objekt verfügt über alle Eigenschaften eines „Person“-Objekts: Es hat dieselben Attribute und Methoden. Diese Attribute und Methoden der übergeordneten Klasse werden in der Definition der untergeordneten Klasse nicht wiederholt; wir geben lediglich die Attribute und Methoden an, die von der untergeordneten Klasse hinzugefügt wurden. Wir gehen davon aus, dass die Klasse „Person“ wie folgt definiert ist:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' class person
Public Class personne
' class attributes
Private Shared _nbPersonnes As Long = 0
' instance attributes
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' manufacturers
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
' one more person
_nbPersonnes += 1
' construction
Me._prenom = P
Me._nom = N
Me._age = age
' follow-up
Console.Out.WriteLine("Construction personne(string, string, int)")
End Sub
Public Sub New(ByVal P As personne)
' one more person
_nbPersonnes += 1
' construction
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
' follow-up
Console.Out.WriteLine("Construction personne(string, string, int)")
End Sub
' class property
Public Shared ReadOnly Property nbPersonnes() As Long
Get
Return _nbPersonnes
End Get
End Property
' instance properties
Public Property prenom() As String
Get
Return _prenom
End Get
Set(ByVal Value As String)
_prenom = Value
End Set
End Property
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = Value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
' valid age?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
Public ReadOnly Property identite() As String
Get
Return "personne(" & _prenom & "," & _nom & "," & age & ")"
End Get
End Property
End Class
Die Methode „Identify“ wurde durch die schreibgeschützte Eigenschaft „Identity“ ersetzt, die die Person identifiziert. Wir erstellen eine Klasse „Teacher“, die von der Klasse „Person“ erbt:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Public Class enseignant
Inherits personne
' attributes
Private _section As Integer
' manufacturer
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
MyBase.New(P, N, age)
Me._section = section
' follow-up
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
' property section
Public Property section() As Integer
Get
Return _section
End Get
Set(ByVal Value As Integer)
_section = Value
End Set
End Property
End Class
Die Klasse „Teacher“ erweitert die Methoden und Attribute der Klasse „Person“ um Folgendes:
- ein Attribut `section`, das die Nummer der Abteilung angibt, zu der der Lehrer innerhalb des Lehrerkollegiums gehört (in der Regel eine Abteilung pro Fach)
- einen neuen Konstruktor, der alle Attribute eines Lehrers initialisiert
Die Deklaration
Public Class enseignant
Inherits personne
bedeutet, dass die Klasse „Teacher“ von der Klasse „Person“ abgeleitet ist.
3.2.2. Erstellen eines Teacher-Objekts
Der Konstruktor für die Klasse „Teacher“ lautet wie folgt:
' manufacturer
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
MyBase.New(P, N, age)
Me._section = section
' follow-up
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
Die Deklaration
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
gibt an, dass der Konstruktor vier Parameter entgegennimmt: P, N, age und section. Er muss drei davon (P, N, age) an seine Basisklasse weitergeben, in diesem Fall an die Klasse Person. Wir wissen, dass diese Klasse über einen Konstruktor Person(string, string, int) verfügt, der es uns ermöglicht, ein Person-Objekt unter Verwendung der übergebenen Parameter (P, N, age) zu erstellen. Die Klasse [Teacher] übergibt die Parameter (P, N, age) wie folgt an ihre Basisklasse:
MyBase.New(P, N, age)
Sobald die Basisklasse instanziiert wurde, wird die Erstellung des Teacher-Objekts mit der Ausführung des Konstruktor-Körpers fortgesetzt:
Me._section = section
Zusammenfassend lässt sich sagen, dass der Konstruktor einer abgeleiteten Klasse:
- übermittelt an seine Basisklasse die Parameter, die er benötigt, um sich selbst zu konstruieren
- verwendet die anderen Parameter, um seine eigenen Attribute zu initialisieren
Wir hätten auch schreiben können:
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
Me._prenom = P
Me._nom = N
Me._age = age
Me._section = section
' follow-up
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
Das ist unmöglich. Die Klasse „Person“ hat ihre drei Felder – _firstName, _lastName und _age – als privat deklariert. Nur Objekte derselben Klasse haben direkten Zugriff auf diese Felder. Alle anderen Objekte, einschließlich untergeordneter Objekte wie in diesem Fall, müssen öffentliche Methoden verwenden, um auf sie zuzugreifen. Dies wäre anders gewesen, wenn die Klasse „Person“ die drei Felder als geschützt deklariert hätte: Dann hätten abgeleitete Klassen direkten Zugriff auf die drei Felder gehabt. In unserem Beispiel war die Verwendung des Konstruktors der übergeordneten Klasse daher die richtige Lösung und entspricht dem Standardansatz: Beim Erstellen eines untergeordneten Objekts rufen wir zunächst den Konstruktor des übergeordneten Objekts auf und führen anschließend die für das untergeordnete Objekt spezifischen Initialisierungen durch (Abschnitt in unserem Beispiel).
Kompilieren wir nun die Klassen „Person“ und „Teacher“ zu Assemblies:
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
Beachten Sie, dass wir zum Kompilieren der untergeordneten Klasse „Teacher“ auf die Datei „person.dll“ verweisen mussten, die die Klasse „Person“ enthält. Versuchen wir es mit einem ersten Testprogramm:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test
Sub Main()
Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite)
End Sub
End Module
Dieses Programm erstellt lediglich ein Teacher-Objekt (new) und identifiziert es. Die Teacher-Klasse verfügt nicht über eine Identity-Methode, aber ihre übergeordnete Klasse hat eine, die zudem öffentlich ist: Durch Vererbung wird sie zu einer öffentlichen Methode der Teacher-Klasse. Die erhaltenen Ergebnisse lauten wie folgt:
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)
Wir sehen, dass:
- ein Person-Objekt vor dem Lehrer-Objekt erstellt wurde
- die ermittelte Identität ist die des Objekts „Person“
3.2.3. Überladen einer Methode oder Eigenschaft
Im vorherigen Beispiel hatten wir die Identität der Person als Teil des Lehrers, aber es fehlen einige für die Klasse „Teacher“ spezifische Informationen (der Abschnitt). Wir müssen daher eine Eigenschaft schreiben, die es uns ermöglicht, den Lehrer zu identifizieren:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Public Class enseignant
Inherits personne
' attributes
Private _section As Integer
' manufacturer
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
MyBase.New(P, N, age)
Me._section = section
' follow-up
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
' property section
Public Property section() As Integer
Get
Return _section
End Get
Set(ByVal Value As Integer)
_section = Value
End Set
End Property
' overload property identity
Public Shadows ReadOnly Property identite() As String
Get
Return "enseignant(" & MyBase.identite & "," & _section & ")"
End Get
End Property
End Class
Die identity-Methode der Teacher-Klasse stützt sich auf die identity-Methode ihrer übergeordneten Klasse (MyBase.identity), um den Teil „person“ anzuzeigen, und ergänzt diesen anschließend um das Feld _section, das spezifisch für die Teacher-Klasse ist. Beachten Sie die Deklaration der identity-Eigenschaft:
Public Shadows ReadOnly Property identite() As String
Dies bedeutet, dass die Eigenschaft „identity“ die gleichnamige Methode „verbirgt“, die möglicherweise in der übergeordneten Klasse vorhanden ist. Betrachten wir ein „Teacher“-Objekt E. Dieses Objekt enthält ein „Person“-Objekt:
![]() |
Die Eigenschaft „identity“ ist sowohl in der Klasse „Teacher“ als auch in ihrer übergeordneten Klasse „Person“ definiert. In der untergeordneten Klasse „Teacher“ muss der Eigenschaft „identity“ das Schlüsselwort „shadows“ vorangestellt werden, um anzugeben, dass wir eine neue Eigenschaft „identity“ für die Klasse „Teacher“ neu definieren.
Public Shadows ReadOnly Property identite() As String
Die Klasse „Teacher“ verfügt nun über zwei Identitäts-Eigenschaften:
- die von der übergeordneten Klasse „Person“ geerbte
- eine eigene
Wenn E ein „Teacher“-Objekt ist, verweist E.identity auf die identity-Methode der „Teacher“-Klasse. Wir sagen, dass die identity-Eigenschaft der übergeordneten Klasse durch die identity-Eigenschaft der untergeordneten Klasse „überschrieben“ wird. Allgemein gilt: Wenn O ein Objekt und M eine Methode ist, sucht das System zur Ausführung der Methode O.M in der folgenden Reihenfolge nach einer Methode M:
- in der Klasse des Objekts O
- in seiner übergeordneten Klasse, falls vorhanden
- in der übergeordneten Klasse seiner übergeordneten Klasse, falls diese existiert
- usw.
Die Vererbung ermöglicht es daher, Methoden/Eigenschaften mit demselben Namen in der übergeordneten Klasse in der untergeordneten Klasse neu zu definieren. Dadurch kann die untergeordnete Klasse an ihre eigenen Bedürfnisse angepasst werden. In Kombination mit dem Polymorphismus, auf den wir gleich noch eingehen werden, ist die Überladung von Methoden/Eigenschaften der Hauptvorteil der Vererbung. Betrachten wir dasselbe Beispiel wie zuvor:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test
Sub Main()
Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite)
End Sub
End Module
Die diesmal erzielten Ergebnisse lauten wie folgt:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Jean,Dupont,30),27)
3.2.4. Polymorphismus
Betrachten wir eine Klassenhierarchie: C0 C1 C2 … Cn, wobei Ci Cj angibt, dass die Klasse Cj von der Klasse Ci abgeleitet ist. Dies bedeutet, dass die Klasse Cj alle Eigenschaften der Klasse Ci sowie zusätzliche Eigenschaften besitzt. Seien Oi Objekte vom Typ Ci. Es ist zulässig zu schreiben:
Tatsächlich besitzt die Klasse Cj durch Vererbung alle Eigenschaften der Klasse Ci sowie zusätzliche Eigenschaften. Daher enthält ein Objekt Oj vom Typ Cj ein Objekt vom Typ Ci. Die Operation
bedeutet, dass Oi ein Verweis auf das im Objekt Oj enthaltene Objekt vom Typ Ci ist.
Die Tatsache, dass eine Variable Oi der Klasse Ci nicht nur auf ein Objekt der Klasse Ci, sondern auf jedes von der Klasse Ci abgeleitete Objekt verweisen kann, wird als Polymorphismus bezeichnet: die Fähigkeit einer Variablen, auf Objekte unterschiedlicher Typen zu verweisen. Betrachten wir ein Beispiel und nehmen wir die folgende Funktion, die unabhängig von jeder Klasse ist:
Sub affiche(ByVal p As personne)
Wir könnten genauso gut schreiben
als
Im letzteren Fall erhält der formale Parameter vom Typ „Person“ in der Anzeigefunktion einen Wert vom Typ „Lehrer“. Da der Typ „Lehrer“ vom Typ „Person“ abgeleitet ist, ist dies zulässig.
3.2.5. Neudefinition und Polymorphismus
Vervollständigen wir unsere Anzeige-Prozedur:
Sub affiche(ByVal p As personne)
' displays identity of p
Console.Out.WriteLine(p.identite)
End Sub
Die Methode p.identity gibt eine Zeichenfolge zurück, die das Person-Objekt identifiziert. Was passiert in unserem vorherigen Beispiel, wenn es sich um ein Teacher-Objekt handelt:
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
Sehen wir uns das folgende Beispiel an:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test
Sub Main()
' a teacher
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' a person
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' poster
Sub affiche(ByVal p As personne)
' displays identity of p
Console.Out.WriteLine(p.identite)
End Sub
End Module
Die erzielten Ergebnisse lauten wie folgt:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(Lucile,Dumas,56)
Construction personne(string, string, int)
personne(Jean,Dupont,30)
Die Ausführung zeigt, dass die Anweisung p.identity jedes Mal die Identitäts-Eigenschaft einer Person auslöste: zuerst die in dem Lehrer e enthaltene Person, dann die Person p selbst. Sie passte sich nicht an das Objekt an, das tatsächlich als Parameter an display übergeben wurde. Wir hätten es vorgezogen, die vollständige Identität des Lehrers e zu erhalten. Um dies zu erreichen, hätte die Notation p.identity auf die Identitätseigenschaft des Objekts verweisen müssen, auf das p tatsächlich zeigt, anstatt auf die Identitätseigenschaft des Teils „person“ des Objekts, auf das p tatsächlich zeigt. Es ist möglich, dieses Ergebnis zu erzielen, indem man identity als überschreibbare Eigenschaft in der Basisklasse Person deklariert:
Public Overridable ReadOnly Property identite() As String
Get
Return "personne(" & _prenom & "," & _nom & "," & age & ")"
End Get
End Property
Das Schlüsselwort overridable macht *identite* zu einer verfeinerbaren oder virtuellen Eigenschaft. Dieses Schlüsselwort kann auch auf Methoden angewendet werden. Unterklassen, die eine virtuelle Eigenschaft oder Methode neu definieren, müssen das Schlüsselwort overrides anstelle von shadows verwenden, um ihre neu definierte Eigenschaft oder Methode zu kennzeichnen. Daher ist in der Klasse enseignant die Eigenschaft identite wie folgt definiert:
' overload property identity
Public Overrides ReadOnly Property identite() As String
Get
Return "enseignant(" & MyBase.identite & "," & _section & ")"
End Get
End Property
Das Testprogramm:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test
Sub Main()
' a teacher
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' a person
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' poster
Sub affiche(ByVal p As personne)
' displays identity of p
Console.Out.WriteLine(p.identite)
End Sub
End Module
ergibt dann folgende Ergebnisse:
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)
Diesmal haben wir erfolgreich die vollständige Identität des Lehrers ermittelt. Definieren wir nun eine Methode statt einer Eigenschaft neu. Die Object-Klasse ist die „übergeordnete“ Klasse aller VB.NET-Klassen. Wenn wir also schreiben:
schreiben wir implizit:
Die Klasse „object“ definiert eine virtuelle ToString-Methode:

Die ToString-Methode gibt den Namen der Klasse zurück, zu der das Objekt gehört, wie im folgenden Beispiel gezeigt:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test2
Sub Main()
' a teacher
Console.Out.WriteLine(New enseignant("Lucile", "Dumas", 56, 61).ToString())
' a person
Console.Out.WriteLine(New personne("Jean", "Dupont", 30).ToString())
End Sub
End Module
Die Ergebnisse lauten wie folgt:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant
Construction personne(string, string, int)
personne
Beachten Sie, dass wir zwar die ToString-Methode in den Klassen Person und Lehrer nicht überschrieben haben, aber dennoch sehen können, dass die ToString-Methode der Klasse Object weiterhin in der Lage ist, den tatsächlichen Klassennamen des Objekts anzuzeigen. Überschreiben wir nun die ToString-Methode in den Klassen Person und Lehrer:
' ToString
Public Overrides Function ToString() As String
' we return the identity property
Return identite
End Function
Die Definition ist in beiden Klassen identisch. Betrachten Sie das folgende Testprogramm:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test
Sub Main()
' a teacher
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' a person
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' poster
Sub affiche(ByVal p As personne)
' displays identity of p
Console.Out.WriteLine(p.identite)
End Sub
End Module
Die Ausführungsergebnisse lauten wie folgt:
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. Definition eines Indexers für eine Klasse
Betrachten Sie die in der .NET-Plattform vordefinierte Klasse [ArrayList]. Diese Klasse ermöglicht es Ihnen, Objekte in einer Liste zu speichern. Sie gehört zum Namespace [System.Collections]. Im folgenden Beispiel verwenden wir diese Klasse, um eine Liste von Personen (im weiteren Sinne) zu speichern:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Imports System.Collections
' class listeDePersonnes
Public Class listeDePersonnes
Inherits ArrayList
' to add a person to the list
Public Overloads Sub Add(ByVal p As personne)
MyBase.Add(p)
End Sub
' an indexer
Default Public Shadows Property Item(ByVal i As Integer) As personne
Get
Return CType(MyBase.Item(i), personne)
End Get
Set(ByVal Value As personne)
MyBase.Item(i) = Value
End Set
End Property
' another indexer
Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
Get
' we search for the person with name N
Dim i As Integer
For i = 0 To Count - 1
If CType(Me(i), personne).nom = N Then
Return i
End If
Next i
Return -1
End Get
End Property
' toString
Public Overrides Function ToString() As String
' render (él1, él2, ..., éln)
Dim liste As String = "("
Dim i As Integer
' browse the dynamic table
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' last element
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
End Class
Hier einige Informationen zu bestimmten Eigenschaften und Methoden der Klasse [ArrayList]:
Eigenschaft, die die Anzahl der Elemente in der Liste zurückgibt | |
Methode zum Hinzufügen eines Objekts zur Liste | |
Methode, die das i-te Element der Liste zurückgibt |
Wir stellen fest, dass wir, um das i-te Element der Liste zu erhalten, nicht [list.Item(i)] geschrieben haben, sondern direkt [list(i)], was auf den ersten Blick falsch erscheint. Dies ist jedoch möglich, da die Klasse [ArrayList] eine Standard-Eigenschaft [Item] mit einer Syntax definiert, die in etwa wie folgt aussieht:
Default Public Property Item(ByVal i As Integer) As Object
Get
...
End Get
Set(ByVal Value As personne)
...
End Set
End Property
Wenn der Compiler auf die Notation [list(i)] stößt, prüft er, ob die Klasse [ArrayList] eine Eigenschaft mit der folgenden Signatur definiert hat:
Default Public Property Proc(ByVal var As Integer) As Type
Hier findet es die Prozedur [Item]. Anschließend wandelt es die Notation [list(i)] in [list.Item(i)] um. Die Eigenschaft [Item] ist die standardmäßige indizierte Eigenschaft der Klasse [ArrayList]. Die Ausführung des vorangegangenen Programms liefert folgende Ergebnisse:
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)
Erstellen wir eine Klasse namens [listOfPeople], die eine Liste von Personen darstellt, also eine spezielle Liste, die sich nahtlos aus der Klasse [ArrayList] ableiten lässt:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Imports System.Collections
' class listeDePersonnes
Public Class listeDePersonnes
Inherits ArrayList
' to add a person to the list
Public Shadows Sub Add(ByVal p As Object)
' p must be a person
If Not TypeOf (p) Is personne Then
Throw New Exception("L'objet ajouté (" + p.ToString + ") n'est pas une personne")
Else
MyBase.Add(p)
End If
End Sub
' an indexer
Default Public Shadows Property Index(ByVal i As Integer) As personne
Get
Return CType(MyBase.Item(i), personne)
End Get
Set(ByVal Value As personne)
MyBase.Item(i) = Value
End Set
End Property
' toString
Public Overrides Function ToString() As String
' render (él1, él2, ..., éln)
Dim liste As String = "("
Dim i As Integer
' browse the dynamic table
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' last element
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
End Class
Die Klasse verfügt über die folgenden Methoden und Eigenschaften:
gibt eine Zeichenfolge zurück, die den Inhalt der Liste „darstellt“ | |
Methode zum Hinzufügen einer Person zur Liste | |
Standardmäßige indizierte Eigenschaft, die die i-te Person in der Liste zurückgibt |
Betrachten wir die neuen Funktionen:
Es wird eine neue Methode [Add] erstellt.
' to add a person to the list
Public Shadows Sub Add(ByVal p As Object)
' p must be a person
If Not TypeOf (p) Is personne Then
Throw New Exception("L'objet ajouté (" + p.ToString + ") n'est pas une personne")
Else
MyBase.Add(p)
End If
End Sub
In der übergeordneten Klasse [ArrayList] gibt es bereits eine Prozedur mit derselben Signatur, daher das Schlüsselwort [Shadows], um anzugeben, dass die neue Prozedur diejenige in der übergeordneten Klasse ersetzt. Die [Add]-Methode der Unterklasse prüft mithilfe der [TypeOf]-Funktion, ob das hinzugefügte Objekt tatsächlich vom Typ [person] oder einem abgeleiteten Typ ist. Ist dies nicht der Fall, wird mit der [Throw]-Anweisung eine Ausnahme ausgelöst. Ist das hinzugefügte Objekt tatsächlich vom Typ [person], wird die [Add]-Methode der Basisklasse verwendet, um das Hinzufügen durchzuführen.
Es wird eine indizierte Eigenschaft erstellt:
' an indexer
Default Public Shadows Property Index(ByVal i As Integer) As personne
Get
Return CType(MyBase.Item(i), personne)
End Get
Set(ByVal Value As personne)
MyBase.Item(i) = Value
End Set
End Property
In der Basisklasse gibt es bereits eine Standard-Eigenschaft namens [Item] mit derselben Signatur. Daher müssen wir das Schlüsselwort [Shadows] verwenden, um anzugeben, dass die neue indizierte Eigenschaft [Index] die Eigenschaft [Item] der Basisklasse „ausblendet“. Beachten Sie, dass dies auch dann gilt, wenn die beiden Eigenschaften nicht denselben Namen haben. Die Eigenschaft [Index] ermöglicht es Ihnen, auf die Person am Index i in der Liste zu verweisen. Sie stützt sich auf die Eigenschaft [Item] der Basisklasse, um auf das Element am Index i des zugrunde liegenden [ArrayList]-Objekts zuzugreifen. Es werden Typänderungen vorgenommen, um der Tatsache Rechnung zu tragen, dass die Eigenschaft [Item] mit Elementen vom Typ [Object] arbeitet, während die Eigenschaft [Index] mit Elementen vom Typ [Person] arbeitet.
Schließlich überschreiben wir die [ToString]-Methode der [ArrayList]-Klasse:
' toString
Public Overrides Function ToString() As String
' render (él1, él2, ..., éln)
Dim liste As String = "("
Dim i As Integer
' browse the dynamic table
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' last element
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
Diese Methode gibt eine Zeichenfolge der Form „(e1,e2,...,en)“ zurück, wobei e1, e2, ..., en die Elemente der Liste sind. Beachten Sie die Notation [Me(i)], die sich auf das i-te Element des aktuellen Objekts [Me] bezieht. Dies ist die standardmäßig verwendete indizierte Eigenschaft. Somit entspricht [Me(i)] [Me.Index(i)].
Der Klassencode wird in die Datei [lstpersonnes2.vb] eingefügt und kompiliert:
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
Ein Testprogramm wird erstellt:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
Imports System.Collections
Module test
Sub Main()
' create an empty list of people
Dim liste As listeDePersonnes = New listeDePersonnes
' creating people
Dim p1 As personne = New personne("paul", "chenou", 31)
Dim p2 As personne = New personne("nicole", "chenou", 11)
Dim e1 As enseignant = New enseignant("jacques", "sileau", 33, 61)
' list filling
liste.Add(p1)
liste.Add(p2)
liste.Add(e1)
' list display
Console.Out.WriteLine(liste.ToString)
' add an object other than person
Try
liste.Add(4)
Catch e As Exception
Console.Error.WriteLine(e.Message)
End Try
End Sub
End Module
Es wird kompiliert:
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
dann ausgeführt:
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
Man möchte vielleicht schreiben
wobei l vom Typ [listOfPeople] wäre. Hier möchten wir die Liste l nicht anhand einer Elementnummer, sondern anhand des Namens einer Person indizieren. Dazu definieren wir eine neue standardmäßig indizierte Eigenschaft:
' another indexer
Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
Get
' we search for the person with name N
Dim i As Integer
For i = 0 To Count - 1
If CType(Me(i), personne).nom = N Then
Return i
End If
Next i
Return -1
End Get
End Property
Die erste Zeile
Default Public Shadows ReadOnly Property Index(ByVal N As String) As Integer
gibt an, dass wir eine neue standardmäßige indizierte Eigenschaft erstellen. Alle Standard-Eigenschaften müssen denselben Namen haben, in diesem Fall [Index]. Die neue [Index]-Eigenschaft indiziert die PeopleList-Klasse anhand einer Zeichenfolge N. Das Ergebnis von PeopleList(N) ist eine Ganzzahl. Diese Ganzzahl gibt die Position der Person mit dem Namen N in der Liste an oder -1, falls diese Person nicht in der Liste enthalten ist. Wir definieren nur die Get-Eigenschaft und verhindern damit die Zuweisung listOfPeople("name")=value, die die Definition der Set-Eigenschaft erfordert hätte. Daher das Schlüsselwort [ReadOnly]. Das Schlüsselwort [Shadows] ist erforderlich, um die Standard-Eigenschaft der Basisklasse auszublenden (auch wenn sie nicht dieselbe Signatur hat).
Im Hauptteil der get-Methode durchlaufen wir die Liste der Personen und suchen nach dem als Parameter übergebenen Namen N. Wenn wir ihn an Position i finden, geben wir i zurück; andernfalls geben wir -1 zurück.
Ein neues Testprogramm könnte wie folgt aussehen:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' test pg
Module test
Sub Main()
' a list of people
Dim l As New listeDePersonnes
' add people
l.Add(New personne("jean", "dumornet", 10))
l.Add(New personne("pauline", "duchemin", 12))
' display
Console.Out.WriteLine(("l=" + l.ToString))
l.Add(New personne("jacques", "tartifume", 27))
Console.Out.WriteLine(("l=" + l.ToString))
' change item 1
l(1) = New personne("sylvie", "cachan", 5)
' display element 1
Console.Out.WriteLine(("l[1]=" + l(1).ToString))
' display list l
Console.Out.WriteLine(("l=" + l.ToString))
' people search
Dim noms() As String = New [String]() {"cachan", "inconnu"}
Dim i As Integer
For i = 0 To noms.Length - 1
Dim inom As Integer = l(noms(i))
If inom <> -1 Then
Console.Out.WriteLine(("personne(" & noms(i) & ")=" & l(inom).ToString))
Else
Console.Out.WriteLine(("personne(" + noms(i) + ") n'existe pas"))
End If
Next i
End Sub
End Module
Die Ausführung liefert folgende Ergebnisse:
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. Strukturen
Die VB.NET-Struktur leitet sich direkt von der C-Struktur ab und ähnelt stark einer Klasse. Eine Struktur wird wie folgt definiert:
Structure spersonne
' attributs
...
' propriétés
...
' constructeurs
...
' méthodes
End Structure
Trotz Ähnlichkeiten in der Syntax gibt es erhebliche Unterschiede zwischen Klassen und Strukturen. So gibt es beispielsweise bei Strukturen kein Konzept der Vererbung. Wenn wir eine Klasse schreiben, die nicht von einer anderen abgeleitet werden soll, welche Unterschiede zwischen Strukturen und Klassen helfen uns dann bei der Entscheidung zwischen den beiden? Schauen wir uns das folgende Beispiel an, um das herauszufinden:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' spersonne structure
Structure spersonne
Public nom As String
Public age As Integer
End Structure
' class cperson
Class cpersonne
Public nom As String
Public age As Integer
End Class
' a test module
Public Module test
Sub Main()
' a person p1
Dim sp1 As spersonne
sp1.nom = "paul"
sp1.age = 10
Console.Out.WriteLine(("sp1=spersonne(" & sp1.nom & "," & sp1.age & ")"))
' a p2 person
Dim sp2 As spersonne = sp1
Console.Out.WriteLine(("sp2=spersonne(" & sp2.nom & "," & sp2.age & ")"))
' sp2 is modified
sp2.nom = "nicole"
sp2.age = 30
' checking sp1 and sp2
Console.Out.WriteLine(("sp1=cpersonne(" & sp1.nom & "," & sp1.age & ")"))
Console.Out.WriteLine(("sp2=cpersonne(" & sp2.nom & "," & sp2.age & ")"))
' a cperson cp1
Dim cp1 As New cpersonne
cp1.nom = "paul"
cp1.age = 10
Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
' a P2 person
Dim cp2 As cpersonne = cp1
Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
' P2 is modified
cp2.nom = "nicole"
cp2.age = 30
' p1 and P2 verification
Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
End Sub
End Module
Wenn wir dieses Programm ausführen, erhalten wir folgende Ergebnisse:
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)
Während wir auf den vorherigen Seiten dieses Kapitels eine Person-Klasse verwendet haben, verwenden wir nun eine PersonStructure:
' structure spersonne
Structure spersonne
Public nom As String
Public age As Integer
End Structure
Die Deklaration
Dim sp1 As spersonne
erstellt eine Struktur (name, age) und der Wert von sp1 ist diese Struktur selbst.
Die Deklaration
Dim cp1 As New cpersonne
erstellt ein Objekt [cpersonne] (das in etwa unserer Struktur entspricht), und cp1 ist dann die Adresse (die Referenz) dieses Objekts.
Zusammenfassend lässt sich sagen
- Im Fall der Struktur ist der Wert von sp1 die Struktur selbst
- im Fall einer Klasse ist der Wert von p1 die Adresse des erstellten Objekts
![]() |
![]() |
Wenn wir im Programm schreiben
Dim sp2 As spersonne = sp1
wird eine neue Struktur (Name, Alter) erstellt und mit dem Wert von p1 initialisiert, d. h. mit der Struktur selbst.
![]() |
Die Struktur von sp1 wird somit in sp2 dupliziert. Dies ist eine Wertkopie.
Die Anweisung
Dim cp2 As cpersonne = cp1
funktioniert anders. Der Wert von cp1 wird in cp2 kopiert, aber da dieser Wert eigentlich die Adresse des Objekts ist, wird das Objekt selbst nicht dupliziert. Es gibt lediglich zwei Verweise, die darauf verweisen:
![]() |
Im Fall der Struktur wird der Wert von sp1 nicht verändert, wenn wir den Wert von sp2 ändern, wie das Programm zeigt. Im Fall des Objekts wird das Objekt, auf das cp1 zeigt, verändert, wenn wir das Objekt ändern, auf das cp2 zeigt, da es sich um dasselbe Objekt handelt. Dies wird ebenfalls durch die Ergebnisse des Programms veranschaulicht.
Aus diesen Erläuterungen können wir daher folgern, dass:
- der Wert einer Variablen vom Typ Struktur die Struktur selbst ist
- der Wert einer Variablen vom Typ Objekt ist die Adresse des Objekts, auf das sie zeigt
Sobald man diesen grundlegenden Unterschied verstanden hat, erweist sich die Struktur als einer Klasse sehr ähnlich, wie das folgende neue Beispiel zeigt:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' structure person
Structure personne
' attributes
Private _nom As String
Private _age As Integer
' properties
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
_age = value
End Set
End Property
' Manufacturer
Public Sub New(ByVal NOM As String, ByVal AGE As Integer)
_nom = NOM
_age = AGE
End Sub 'New
' TOSTRING
Public Overrides Function ToString() As String
Return "personne(" & nom & "," & age & ")"
End Function
End Structure
' a test module
Module test
Sub Main()
' one person p1
Dim p1 As New personne("paul", 10)
Console.Out.WriteLine(("p1=" & p1.ToString))
' one person p2
Dim p2 As personne = p1
Console.Out.WriteLine(("p2=" & p2.ToString))
' p2 is modified
p2.nom = "nicole"
p2.age = 30
' checking p1 and p2
Console.Out.WriteLine(("p1=" & p1.ToString))
Console.Out.WriteLine(("p2=" & p2.ToString))
End Sub
End Module
Es werden folgende Ausführungsergebnisse erhalten:
Der einzige nennenswerte Unterschied zwischen einer Struktur und einer Klasse besteht darin, dass bei einer Klasse die Objekte p1 und p2 am Ende des Programms denselben Wert gehabt hätten, nämlich den von p2.
3.5. Schnittstellen
Eine Schnittstelle ist eine Sammlung von Methoden- oder Eigenschaftsprototypen, die einen Vertrag bilden. Eine Klasse, die sich entscheidet, eine Schnittstelle zu implementieren, verpflichtet sich, eine Implementierung aller in der Schnittstelle definierten Methoden bereitzustellen. Der Compiler überprüft diese Implementierung. Hier ist zum Beispiel die Definition der Schnittstelle Istats:
Jede Klasse, die diese Schnittstelle implementiert, wird wie folgt deklariert:
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
Die Methoden [mean] und [standardDev] müssen in der Klasse C definiert werden. Betrachten Sie den folgenden Code:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' structure
Public Structure élève
Public _nom As String
Public _note As Double
' manufacturer
Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
Me._nom = NOM
Me._note = NOTE
End Sub
End Structure
' class notes
Public Class notes
' attribute
Protected _matière As String
Protected _élèves() As élève
' manufacturer
Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
' student & subject memorization
Me._matière = MATIERE
Me._élèves = ELEVES
End Sub
' ToString
Public Overrides Function ToString() As String
Dim valeur As String = "matière=" + _matière + ", notes=("
Dim i As Integer
' concatenate all the notes
For i = 0 To (_élèves.Length - 1) - 1
valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "],"
Next i
'final note
If _élèves.Length <> 0 Then
valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "]"
End If
valeur += ")"
' end
Return valeur
End Function
End Class
Die Klasse „notes“ erfasst die Noten für eine Klasse in einem Fach:
Public Class notes
' attribut
Protected _matière As String
Protected _élèves() As élève
Die Attribute sind als „protected“ deklariert, damit von einer abgeleiteten Klasse aus auf sie zugegriffen werden kann. Der Typ „student“ ist eine Struktur, die den Namen des Schülers und dessen Note in dem jeweiligen Fach speichert:
Public Structure élève
Public _nom As String
Public _note As Double
' manufacturer
Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
Me._nom = NOM
Me._note = NOTE
End Sub
End Structure
Wir beschließen, diese Notes-Klasse in eine notesStats-Klasse abzuleiten, die zwei zusätzliche Attribute haben soll: den Durchschnitt und die Standardabweichung der Noten:
Public Class notesStats
Inherits notes
Implements Istats
' attributes
Private _moyenne As Double
Private _écartType As Double
Die Klasse „notesStats“ implementiert die folgende Istats-Schnittstelle:
Das bedeutet, dass die Klasse notesStats zwei Methoden namens average und standardDeviation mit den in der Schnittstelle Istats angegebenen Signaturen haben muss. Die Klasse notesStats sieht wie folgt aus:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Public Class notesStats
Inherits notes
Implements Istats
' attributes
Private _moyenne As Double
Private _écartType As Double
' manufacturer
Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
MyBase.New(MATIERE, ELEVES)
' average score calculation
Dim somme As Double = 0
Dim i As Integer
For i = 0 To ELEVES.Length - 1
somme += ELEVES(i)._note
Next i
If ELEVES.Length <> 0 Then
_moyenne = somme / ELEVES.Length
Else
_moyenne = -1
End If
' standard deviation
Dim carrés As Double = 0
For i = 0 To ELEVES.Length - 1
carrés += Math.Pow(ELEVES(i)._note - _moyenne, 2)
Next i
If ELEVES.Length <> 0 Then
_écartType = Math.Sqrt((carrés / ELEVES.Length))
Else
_écartType = -1
End If
End Sub
' ToString
Public Overrides Function ToString() As String
Return MyBase.ToString() & ",moyenne=" & _moyenne & ",écart-type=" & _écartType
End Function 'ToString
' istats interface methods
Public Function moyenne() As Double Implements Istats.moyenne
' makes the average score
Return _moyenne
End Function
Public Function écartType() As Double Implements Istats.écartType
' makes the standard deviation
Return _écartType
End Function
End Class
Der Mittelwert _mean und die Standardabweichung _standardDev werden bei der Erstellung des Objekts berechnet. Daher geben die Methoden mean und standardDev einfach die Werte der Attribute _mean und _standardDev zurück. Beide Methoden geben -1 zurück, wenn das Array student leer ist.
Die folgende Testklasse:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test
Sub Main()
' some students & notes
Dim ELEVES() As élève = {New élève("paul", 14), New élève("nicole", 16), New élève("jacques", 18)}
' recorded in a notes object
Dim anglais As New notes("anglais", ELEVES)
' and display
Console.Out.WriteLine((anglais.ToString))
' idem with mean and standard deviation
anglais = New notesStats("anglais", ELEVES)
Console.Out.WriteLine((anglais.ToString))
End Sub
End Module
ergibt folgende Ergebnisse:
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
Die Klasse notesStats hätte die Methoden average und standardDev sehr wohl selbst implementieren können, ohne anzugeben, dass sie die Schnittstelle Istats implementiert. Was ist der Sinn von Schnittstellen? Er ist folgender: Eine Funktion kann als formalen Parameter einen Wert vom Typ I akzeptieren. Jedes Objekt der Klasse C, das die Schnittstelle I implementiert, kann dann ein tatsächlicher Parameter dieser Funktion sein. Betrachten Sie das folgende Beispiel:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' an Iexample interface
Public Interface Iexemple
Function ajouter(ByVal i As Integer, ByVal j As Integer) As Integer
Function soustraire(ByVal i As Integer, ByVal j As Integer) As Integer
End Interface
' a 1st class
Public Class classe1
Implements Iexemple
Public Function ajouter(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.ajouter
Return a + b + 10
End Function
Public Function soustraire(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.soustraire
Return a - b + 20
End Function
End Class
'a 2nd class
Public Class classe2
Implements Iexemple
Public Function ajouter(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.ajouter
Return a + b + 100
End Function
Public Function soustraire(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.soustraire
Return a - b + 200
End Function
End Class
Die Schnittstelle „Iexample“ definiert zwei Methoden: „add“ und „subtract“. Die Klassen „Class1“ und „Class2“ implementieren diese Schnittstelle. Beachten Sie, dass diese Klassen zur Vereinfachung des Beispiels keine weiteren Aufgaben ausführen. Betrachten Sie nun das folgende Beispiel:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' test class
Module test
'calculate
Sub calculer(ByVal i As Integer, ByVal j As Integer, ByVal inter As Iexemple)
Console.Out.WriteLine(inter.ajouter(i, j))
Console.Out.WriteLine(inter.soustraire(i, j))
End Sub
' the Hand function
Sub Main()
' creation of two objects class1 and class2
Dim c1 As New classe1
Dim c2 As New classe2
' static function calls calculate
calculer(4, 3, c1)
calculer(14, 13, c2)
End Sub
End Module
Die Funktion „calculate“ akzeptiert ein Element vom Typ Iexample als Parameter. Sie kann daher für diesen Parameter entweder ein Objekt vom Typ class1 oder class2 entgegennehmen. Dies geschieht in der Prozedur „Main“ mit folgenden Ergebnissen:
Wir sehen also, dass diese Eigenschaft dem Polymorphismus ähnelt, den wir im Zusammenhang mit Klassen besprochen haben. Wenn eine Gruppe von Klassen Ci, die nicht durch Vererbung miteinander verbunden sind (und daher keinen auf Vererbung basierenden Polymorphismus nutzen können), eine Reihe von Methoden mit derselben Signatur gemeinsam hat, kann es sinnvoll sein, diese Methoden in einer Schnittstelle I zusammenzufassen, von der alle relevanten Klassen erben würden. Instanzen dieser Klassen Ci können dann als Parameter für Funktionen verwendet werden, die einen Parameter vom Typ I akzeptieren, d. h. Funktionen, die nur die in der Schnittstelle I definierten Methoden der Ci-Objekte nutzen und nicht die spezifischen Attribute und Methoden der verschiedenen Ci-Klassen. Beachten Sie schließlich, dass Schnittstellenvererbung mehrfach sein kann, d. h. wir können schreiben
Public Class classe
Implements I1,I2,...
wobei die Ij Schnittstellen sind.
3.6. Namensräume
Um eine Zeile auf den Bildschirm zu schreiben, verwenden wir die Anweisung
Wenn wir uns die Definition der Console-Klasse ansehen
Namespace: System
Assembly: Mscorlib (in Mscorlib.dll)
sehen wir, dass sie Teil des System-Namespace ist. Das bedeutet, dass die Console-Klasse als System.Console bezeichnet werden sollte, und wir sollten eigentlich schreiben:
Wir vermeiden dies durch die Verwendung einer Import-Klausel:
Wir sagen, dass wir den System-Namespace mithilfe der imports-Klausel importieren. Wenn der Compiler auf einen Klassennamen stößt (hier: Console), sucht er diesen in den verschiedenen Namespaces, die durch die imports-Klauseln importiert wurden. Hier findet er die Console-Klasse im System-Namespace. Beachten wir nun die zweite Information, die mit der Console-Klasse verbunden ist:
Assembly: Mscorlib (in Mscorlib.dll)
Diese Zeile gibt an, in welcher „Assembly“ sich die Definition der Klasse „Console“ befindet. Wenn Sie außerhalb von Visual Studio.NET kompilieren und die Verweise auf die verschiedenen DLLs angeben müssen, die die von Ihnen benötigten Klassen enthalten, kann diese Information nützlich sein. Denken Sie daran, dass Sie zum Verweisen auf die zum Kompilieren einer Klasse erforderlichen DLLs Folgendes schreiben:
Beim Erstellen einer Klasse können Sie diese innerhalb eines Namespace erstellen. Der Zweck dieser Namespaces besteht darin, Namenskonflikte zwischen Klassen zu vermeiden, beispielsweise wenn diese verkauft werden. Stellen Sie sich zwei Unternehmen vor, E1 und E2, die Klassen vertreiben, die jeweils in den DLLs E1.dll und E2.dll verpackt sind. Angenommen, ein Kunde C erwirbt diese beiden Klassensätze, in denen beide Unternehmen eine Person-Klasse definiert haben. Kunde C kompiliert ein Programm wie folgt:
Wenn die Quelldatei „prog.vb“ die Klasse „Person“ verwendet, weiß der Compiler nicht, ob er die Klasse „Person“ aus der DLL „E1.dll“ oder aus der DLL „E2.dll“ verwenden soll. Er gibt einen Fehler aus. Wenn Unternehmen E1 darauf achtet, seine Klassen in einem Namespace namens E1 und Unternehmen E2 in einem Namespace namens E2 zu erstellen, werden die beiden Person-Klassen dann E1.Person und E2.Person heißen. Der Client muss in seinen Klassen entweder E1.Person oder E2.Person verwenden, nicht jedoch Person. Der Namespace beseitigt diese Mehrdeutigkeit. Um eine Klasse in einem Namespace zu erstellen, schreiben Sie:
Namespace istia.st
Public Class personne
' définition de la classe
...
end Class
end Namespace
Für dieses Beispiel erstellen wir die Klasse „Person“, die wir zuvor behandelt haben, in einem Namespace. Wir wählen „istia.st“ als Namespace. Die Klasse „Person“ sieht dann wie folgt aus:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' creation of istia.st namespace
Namespace istia.st
Public Class personne
' attributes
Private prenom As String
Private nom As String
Private age As Integer
' method
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' method
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
End Namespace
Diese Klasse wird in die Datei „person.dll“ kompiliert:
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
Verwenden wir nun die Person-Klasse in einer Testklasse:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Imports istia.st
' pg test
Public Module test
Sub Main()
Dim p1 As New personne
p1.initialise("Jean", "Dupont", 30)
p1.identifie()
End Sub
End Module
Um das Schreiben zu vermeiden
Dim p1 As New istia.st.personne
Wir haben den Namespace „istia.st“ mit einer „Imports“-Klausel importiert:
Imports istia.st
Nun kompilieren wir das Testprogramm:
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
Dadurch wird eine Datei „test.exe“ erstellt, die bei Ausführung die folgenden Ergebnisse liefert:
3.7. Beispiel zur Steuerberechnung
Wir greifen die bereits im vorigen Kapitel behandelte Steuerberechnung wieder auf und bearbeiten sie mithilfe einer Klasse. Sehen wir uns das Problem noch einmal an:
Wir betrachten den vereinfachten Fall eines Steuerpflichtigen, der nur sein Gehalt anzugeben hat:
- Wir berechnen die Anzahl der Steuerklassen für den Arbeitnehmer als nbParts = nbEnfants / 2 + 1, wenn er unverheiratet ist, und als nbEnfants / 2 + 2, wenn er verheiratet ist, wobei nbEnfants die Anzahl der Kinder ist.
- Wenn er mindestens drei Kinder hat, erhält er einen zusätzlichen halben Anteil
- Wir berechnen sein zu versteuerndes Einkommen R = 0,72 * S, wobei S sein Jahresgehalt ist
- Wir berechnen ihren Familienkoeffizienten QF = R / nbParts
- Wir berechnen ihre Steuer I. Betrachten Sie die folgende Tabelle:
12620,0 | 0 | 0 |
13.190 | 0,05 | 631 |
15.640 | 0,1 | 1.290,5 |
24.740 | 0,15 | 2.072,5 |
31.810 | 0,2 | 3.309,5 |
39.970 | 0,25 | 4.900 |
48.360 | 0,3 | 6.898,5 |
55.790 | 0,35 | 9.316,5 |
92.970 | 0,4 | 12.106 |
127.860 | 0,45 | 16.754,5 |
151.250 | 0,50 | 23.147,5 |
172.040 | 0,55 | 30.710 |
195.000 | 0,60 | 39.312 |
0 | 0,65 | 49.062 |
Jede Zeile enthält 3 Felder. Um die Steuer I zu berechnen, suchen Sie die erste Zeile, in der QF <= Feld1 ist. Wenn beispielsweise QF = 23.000 ist, lautet die gefundene Zeile
Steuer I ist dann gleich 0,15*R – 2072,5*nbParts. Wenn QF so ist, dass die Bedingung QF<=field1 nie erfüllt ist, werden die Koeffizienten aus der letzten Zeile verwendet. Hier:
was die Steuer I = 0,65 * R - 49062 * nbParts ergibt.
Die Klasse **impot** wird wie folgt definiert:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Public Class impot
' data required for tax calculation
' come from an external source
Private limites(), coeffR(), coeffN() As Decimal
' manufacturer
Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
' check that the 3 arrays have the same size
Dim OK As Boolean = LIMITES.Length = COEFFR.Length And LIMITES.Length = COEFFN.Length
If Not OK Then
Throw New Exception("Les 3 tableaux fournis n'ont pas la même taille(" & LIMITES.Length & "," & COEFFR.Length & "," & COEFFN.Length & ")")
End If
' it's good
Me.limites = LIMITES
Me.coeffR = COEFFR
Me.coeffN = COEFFN
End Sub
' tAX CALCULATION
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
' calculating the number of shares
Dim nbParts As Decimal
If marié Then
nbParts = CDec(nbEnfants) / 2 + 2
Else
nbParts = CDec(nbEnfants) / 2 + 1
End If
If nbEnfants >= 3 Then
nbParts += 0.5D
End If
' calculation of taxable income & family quota
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' tAX CALCULATION
limites((limites.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limites(i)
i += 1
End While
Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
End Function
End Class
Ein Steuerobjekt wird mit den Daten erstellt, die zur Berechnung der Steuer eines Steuerpflichtigen benötigt werden. Dies ist der feste Teil des Objekts. Sobald dieses Objekt erstellt ist, kann seine **calculate**-Methode wiederholt aufgerufen werden, um die Steuer des Steuerpflichtigen basierend auf seinem Familienstand (verheiratet oder nicht), der Anzahl der Kinder und dem Jahresgehalt zu berechnen. Ein Testprogramm könnte wie folgt aussehen:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Imports Microsoft.VisualBasic
Module test
Sub Main()
' interactive tax calculator
' the user enters three data points on the keyboard: married nbEnfants salary
' the program then displays the tax payable
Const syntaxe As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
' data tables required for tax calculation
Dim limites() As Decimal = {12620D, 13190D, 15640D, 24740D, 31810D, 39970D, 48360D, 55790D, 92970D, 127860D, 151250D, 172040D, 195000D, 0D}
Dim coeffR() As Decimal = {0D, 0.05D, 0.1D, 0.15D, 0.2D, 0.25D, 0.3D, 0.35D, 0.4D, 0.45D, 0.5D, 0.55D, 0.6D, 0.65D}
Dim coeffN() As Decimal = {0D, 631D, 1290.5D, 2072.5D, 3309.5D, 4900D, 6898.5D, 9316.5D, 12106D, 16754.5D, 23147.5D, 30710D, 39312D, 49062D}
' tax object creation
Dim objImpôt As impot = Nothing
Try
objImpôt = New impot(limites, coeffR, coeffN)
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(1)
End Try
' infinite loop
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Long
While True
' tax calculation parameters are requested
Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
Dim paramètres As String = Console.In.ReadLine().Trim()
' anything to do?
If paramètres Is Nothing OrElse paramètres = "" Then
Exit While
End If
' check the number of arguments in the input line
Dim erreur As Boolean = False
Dim args As String() = paramètres.Split(Nothing)
Dim nbParamètres As Integer = args.Length
If nbParamètres <> 3 Then
Console.Error.WriteLine(syntaxe)
erreur = True
End If
' checking parameter validity
If Not erreur Then
' married
marié = args(0).ToLower()
If marié <> "o" And marié <> "n" Then
erreur = True
End If
' nbEnfants
Try
nbEnfants = Integer.Parse(args(1))
If nbEnfants < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
' salary
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
End If
' if the parameters are correct - the tax is calculated
If Not erreur Then
Console.Out.WriteLine(("impôt=" & objImpôt.calculer(marié = "o", nbEnfants, salaire) & " F"))
Else
Console.Error.WriteLine(syntaxe)
End If
End While
End Sub
End Module
Hier ist ein Beispiel für die Anwendung des vorherigen Programms:
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 :





