3.5 Objektinitialisierung mit Konstruktoren 

3.5.1 Objektkonstruktoren 

Jede Objekterzeugung läuft immer über einen Konstruktor, der durch New aufgerufen wird. Dies gilt auch für alle bisher erzeugten Objekte. Wie ist das möglich, da wir noch keinen Konstruktor geschrieben haben? Die Lösung ist einfach. Wenn kein benutzerdefinierter Konstruktor existiert, stellt Visual Basic einen öffentlichen ohne Parameter zur Verfügung, der nichts macht (außer den Konstruktor des Typs aufzurufen, auf dem die Klasse basiert, siehe Abschnitt 3.13.3, »Zugriff auf Eltern mit MyBase«).
Bisher haben wir immer diesen sogenannten parameterlosen Standardkonstruktor verwendet und die Zustände des Objekts nach dessen Erzeugung geändert. Wenn nun die Standardwerte für Felder des Objekts (siehe Abschnitt 2.5.6, »Initialisierung von Variablen«) einen bezüglich der Programmlogik ungültigen Zustand darstellen, führt die bisherige Vorgehensweise zu einem »ungültigen« frisch erzeugten Objekt, bis die Zustände korrekt gesetzt worden sind. Das ist recht unbefriedigend. Besser ist es, ein Objekt nur so erstellen zu können, dass es von Beginn an »gültig« ist. Dies erreichen wir durch die Definition eigener Konstruktoren mit folgender Syntax (optionale Teile stehen in eckigen Klammern, kursive Teile müssen Sie Ihren Bedürfnissen anpassen):
[<Modifikatoren>] Sub New([<Parameter>]) |
Hinweis |
Die Verwendung von ParamArray und Optional ist erlaubt. |
Der Name New ist zwingend, während die Parameter optional sind. Wie Tabelle 3.6 zeigt, sind bei Konstruktoren weniger Modifikatoren erlaubt als bei Methoden.
Art | Beschreibung |
Sichtbarkeit |
Grad der Öffentlichkeit (siehe Abschnitt 3.2, »Kapselung«) |
Bindung |
Objekt- oder klassenbezogen (siehe Abschnitt 3.4, »Bindung«) |
Das folgende Codefragment definiert ein Rechteck, das nur über die Angabe von Breite und Länge mit New erzeugt werden kann. Jeder Versuch, das Rechteck mit keinen oder anderen Parametern zu erzeugen, wird vom Compiler zurückgewiesen. Also können Angaben nicht »vergessen« werden. Die If-Operatoren im Konstruktor stellen sicher, dass nur gültige Rechtecke erzeugt werden können. Da die Felder a und b privat sind, lässt sich das Rechteck nach dessen Erzeugung von außen nicht mehr ändern. Da außerdem die Klasse selbst keine Methode zum Ändern bereitstellt, ist das geschaffene Rechteck unveränderlich.
'...\Klassendesign\Konstruktoren\NurParametrisiert.vb |
Option Explicit On
Namespace Klassendesign
Class FestesRechteck
Private a, b As Double
Sub New(ByVal a As Double, ByVal b As Double)
Me.a = If(a > 0, a, 1)
Me.b = If(b > 0, b, 1)
End Sub
Sub Drucken()
Console.WriteLine("Rechteck {0}x{1}", a, b)
End Sub
End Class
Module FesteRechtecke
Sub Test()
Dim re1 As FestesRechteck = New FestesRechteck(7, 3)
re1.Drucken()
Dim re2 As FestesRechteck = New FestesRechteck(7, 0)
re2.Drucken()
Console.ReadLine()
End Sub
End Module
End Namespace
Die Ausgabe zeigt, dass die ungültige Breite des zweiten Rechtecks automatisch durch den If-Operator im Konstruktor korrigiert wurde.
Rechteck 7x3
Rechteck 7x1
Ein Konstruktor muss indirekt über New aufgerufen werden und kann nicht direkt aufgerufen werden. Die folgenden Zeilen führen zu einem Compilerfehler:
Dim re As FestesRechteck = New FestesRechteck(7, 3)
re.New(7, 3) 'Fehler!!
Nur in Konstruktoren definierte Parametermuster dürfen verwendet werden. Die folgenden Zeilen führen zu einem Compilerfehler:
Dim re As FestesRechteck = New FestesRechteck() 'falsch
Dim re As FestesRechteck = New FestesRechteck(7) 'falsch
Dim re As FestesRechteck = New FestesRechteck(7,3,1) 'falsch
Hinweis |
Der parameterlose Konstruktor existiert nur in Klassen, die keine benutzerdefinierten Konstruktoren haben oder ihn explizit definieren. |
Wollen Sie also neben Konstruktoren mit Parametern auch einen Standardkonstruktor nutzen können, müssen Sie der Klasse einen solchen explizit spendieren, wie im folgenden Codefragment gezeigt ist:
Class FestesRechteck
...
Sub New()
...
End Sub
Sub New(ByVal a As Double, ByVal b As Double)
...
End Sub
End Class
Verkettung
Konstruktoren lassen sich wie Methoden überladen (siehe Abschnitt 3.3.4, »Überladung (OLs)«). Damit liegen verschiedene Definitionen parallel vor. Normalerweise kann ein Konstruktor nicht direkt aufgerufen werden. Eine Verkettung von Konstruktoren ist nur als erste Anweisung eines Konstruktors derselben Klasse erlaubt (oder einer Kindklasse, siehe Abschnitt 3.13.3, »Zugriff auf Eltern mit MyBase«) und hat eine der beiden folgenden Syntaxen (welche Sie verwenden, ist reine Geschmackssache; eckige Klammern kennzeichnen im Folgenden optionale Teile, kursive Teile müssen Sie Ihren Bedürfnissen anpassen):
Me.New([<Parameter>]) |
Der Grund für die Beschränkung liegt in der Überlegung, dass ein Objekt zuerst initialisiert werden sollte, bevor irgendetwas anderes gemacht wird. Außerdem wird so eine zweifache Initialisierung vermieden. Dies reduziert die Anzahl logischer Fehler aufgrund nicht oder falsch initialisierter Objekte. Das nächste Codefragment nutzt eine Verkettung der Konstruktoren, um die Spezifikation des Rechtecks komfortabel zu machen: null, ein oder zwei Werte sind erlaubt.
'...\Klassendesign\Konstruktoren\Verkettet.vb |
Option Explicit On
Namespace Klassendesign
Class BequemesRechteck
Private a, b As Double
Sub New()
Me.New(5) 'oder MyClass.New(5)
End Sub
Sub New(ByVal a As Short)
MyClass.New(a, 10) 'oder Me.New(a, 10)
End Sub
Sub New(ByVal a As Short, ByVal b As Short)
Me.a = If(a > 0, a, 1)
Me.b = If(b > 0, b, 1)
End Sub
Sub Drucken()
Console.WriteLine("Rechteck {0}x{1}", a, b)
End Sub
End Class
Module BequemeRechtecke
Sub Test()
Dim re As BequemesRechteck
re = New BequemesRechteck(7, 3) : re.Drucken()
re = New BequemesRechteck(7, –3) : re.Drucken()
re = New BequemesRechteck(7) : re.Drucken()
re = New BequemesRechteck(-7) : re.Drucken()
re = New BequemesRechteck() : re.Drucken()
Console.ReadLine()
End Sub
End Module
End Namespace
Die Ausgabe zeigt, dass eine fehlende Breite (einparametriger Konstruktor des dritten und vierten Objekts) mittels der Verkettung MyClass.New(a, 10) durch 10 ersetzt wird. Fehlt außerdem die Länge (parameterloser Konstruktor des letzten Objekts), wird sie mittels der Verkettung Me.New(5) durch 5 ersetzt. Die zweite und vierte Ausgabe zeigen die Ersetzung eines ungültigen Wertes durch 1.
Rechteck 7x3
Rechteck 7x1
Rechteck 7x10
Rechteck 1x10
Rechteck 5x10
Das Erfordernis, dass eine Konstruktorverkettung die erste Anweisung sein muss, kann nicht umgangen werden. Zum Beispiel ist es nicht möglich, das zu erstellende Objekt (implizit) in den aktuellen Parametern von New zu verwenden. Der folgende Code würde vom Compiler zurückgewiesen:
Function Seiteneffekt() As Short
Return 5
End Function
Sub New()
Me.New(Seiteneffekt()) 'Fehler!!
End Sub
Fehlerbehandlung
Bei der Erzeugung eines Objekts kann sich herausstellen, dass die an den Konstruktor übergebenen Parameter ungeeignet sind. Die Möglichkeit, in einem solchen Fall auf Standardwerte zurückzugreifen, haben wir im letzten Abschnitt gesehen. Eine Alternative stellt das Auslösen einer Ausnahme dar, um »ungültige« Konstellationen komplett zurückzuweisen. Das nächste Codefragment testet eine solche abgelehnte Objekterzeugung.
'...\Klassendesign\Konstruktoren\Ausnahme.vb |
Option Explicit On
Namespace Klassendesign
Class UngültigesRechteck
Private a, b As Double
Sub New(ByVal a As Double, ByVal b As Double)
If a <= 0 OrElse b <= 0 Then Throw New ArgumentException()
Me.a = a : Me.b = b
End Sub
End Class
Module UngültigeRechtecke
Sub Test()
Dim re As UngültigesRechteck
Try
re = New UngültigesRechteck(-7, 3)
Catch ex As Exception
Console.WriteLine("Benutzbar {0}", re IsNot Nothing)
End Try
Console.ReadLine()
End Sub
End Module
End Namespace
Die Ausgabe zeigt, dass re noch seinen initialen Wert Nothing hat (siehe Abschnitt 2.5.6, »Initialisierung von Variablen«), das Objekt also gar nicht erzeugt wurde.
Benutzbar False
Hinweis |
Objekte, die vor der Ausnahme im Konstruktor erzeugt wurden, bleiben erhalten. Das Objekt, das durch den Konstuktor erzeugt werden sollte, wird dann nicht erzeugt. |
Initialisierungsliste
Die Felder eines Objekts können auch in Form benannter Parameter initialisiert werden, und zwar mit folgender Syntax (kursive Teile müssen Sie Ihren Bedürfnissen anpassen):
New typ(...) With {.name = wert, ...} |
Hinweis |
Die Felder müssen sichtbar genug sein, damit darauf zugegriffen werden kann (siehe Abschnitt 3.2, »Kapselung«). Die Initialisierung darf auch nur teilweise erfolgen. |
Dadurch werden nach der Ausführung des Konstruktors den gegebenen Objektvariablen neue Werte zugewiesen. Eine Objektvariable in dieser Liste erhält immer einen führenden Punkt, egal ob sie auf der rechten oder linken Seite einer Zuweisung steht. Auf der rechten Seite einer Zuweisung werden die zum Zuweisungszeitpunkt gültigen Werte genommen. Das folgende Codefragment verwendet solche Initialisierungslisten. Die Druckanweisung im Konstruktor protokolliert die Werte am Ende des Konstruktors und vor Beginn der Initialisierungsliste.
'...\Klassendesign\Konstruktoren\Initialisierungsliste.vb |
Option Explicit On
Namespace Klassendesign
Class InitRect
Friend a, b As Double
Sub New(ByVal a As Double, ByVal b As Double)
Me.a = If(a > 0, a, 1)
Me.b = If(b > 0, b, 1)
Console.Write("{{a,b}} {{{0},{1}}} ", Me.a, Me.b)
End Sub
Sub Druck()
Console.WriteLine("Rechteck {0}x{1}", a, b)
End Sub
End Class
Module IntialisierteRechtecke
Sub Test()
Dim r As InitRect
r = New InitRect(7, 3) With {.a = –2} : r.Druck()
r = New InitRect(-7, 3) With {.b = –2} : r.Druck()
r = New InitRect(7, 3) With {.b=.a, .a=2} : r.Druck()
Console.ReadLine()
End Sub
End Module
End Namespace
Die erste Ausgabe zeigt, dass die Werte in den Initialisierungslisten die Angaben in den Konstruktorparametern überschreiben. Durch die Protokollierung am Konstruktorende sieht man in der zweiten Ausgabe, dass der negative Wert korrigiert wurde. Da die Zuweisungslisten den Konstruktor nicht durchlaufen, wird der Wert für b nicht korrigiert und bleibt negativ. Die dritte Initialisierung besteht aus insgesamt vier Zuweisungen (in korrekter Reihenfolge): a = 7 und b = 3 im Konstruktor, b = 7 in der Initialisierungsliste (.b = .a) mit dem gerade gültigen Wert für a, a = 2 in der Initialisierungsliste.
{a,b} {7,3} Rechteck –2x3
{a,b} {1,3} Rechteck 1x-2
{a,b} {7,3} Rechteck 2x7
Hinweis |
Neben Objektfeldern dürfen auch Eigenschaften in der Initialisierungsliste verwendet werden (siehe Abschnitt 3.7, »Eigenschaften«). |
3.5.2 Nichtöffentliche Konstrukturen 

Über die Sichtbarkeitsmodifikatoren eines Konstruktors kann verhindert werden, dass Objekte über New erzeugt werden können. Gibt es keinen Konstruktor mit ausreichender Sichtbarkeit, können Sie auch kein Objekt erzeugen. Dies hindert jedoch die Klasse nicht daran, ein oder mehrere Objekte zu erzeugen. Wollen Sie Vererbung forcieren, können Sie den Konstruktor mit Protected kennzeichnen, sodass nur Objekte von Klassen erzeugt werden können, die auf Ihrer Klasse basieren, Ihre Klasse selbst ist aber nicht instanzierbar machen (siehe Abschnitt 3.13.3, »Zugriff auf Eltern mit MyBase«). Schauen wir uns an einem Beispiel das Konzept nichtöffentlicher Konstruktoren an.
Fabrikmethode und Singleton
Es gibt Situationen, in denen es nicht sinnvoll ist, wenn beliebig viele Objekte einer Klasse existieren. Zum Beispiel würden sich mehrere Fenstermanager auf dem Desktop ins Gehege kommen. In solchen Fällen wird die Anzahl erzeugbarer Objekte beschränkt. Dazu wird zuerst ein nichtöffentlicher Konstruktor definiert, um eine Objekterzeugung mittels des öffentlichen Standardkonstruktors zu verhindern, der immer dann vorhanden ist, wenn kein benutzerdefinierter Konstruktor existiert. Dann wird eine öffentliche Methode zur Verfügung gestellt, die ein Objekt zurückgibt. So eine Methode wird Fabrikmethode genannt, da in ihr ein Objekt erzeugt wird. Da alle Objekte der Klasse über diese eine Methode erzeugt werden, kann sie steuern, wie und ob Objekte erzeugt werden. Im Fall eines Singletons wird nur beim ersten Aufruf der Fabrikmethode ein Objekt erzeugt. Bei jedem späteren Aufruf wird eine Referenz auf das im ersten Aufruf erzeugte Objekt zurückgeliefert. Damit das Ganze funktioniert, muss die Fabrikmethode mit Shared an die Klasse gebunden sein, denn es existiert ja keine Möglichkeit, selbst ein Objekt zu erstellen.
Das folgende Codefragment zeigt ein solches Singleton:
'...\Klassendesign\Konstruktoren\Klasse.vb |
Option Explicit On
Namespace Klassendesign
Class Manager
Private Shared manager As Manager
Private Sub New()
End Sub
Public Shared Function Objekt() As Manager
If manager Is Nothing Then manager = New Manager()
Return manager
End Function
End Class
Module Singleton
Sub Test()
Dim m1, m2 As Manager
m1 = Manager.Objekt()
m2 = Manager.Objekt()
Console.WriteLine("Dasselbe {0}", m1 Is m2)
Console.ReadLine()
End Sub
End Module
End Namespace
Die Ausgabe bestätigt, dass nur ein Objekt im Spiel ist:
Dasselbe True
3.5.3 Grafikbibliothek: Initialisierung des Rechtecks 

In diesem Abschnitt erweitern wir die in Abschnitt 3.1.12, »Grafikbibliothek: Beispiel für Kapitel 3 und 4«, eingeführte und zuletzt in Abschnitt 3.4.4, Grafikbibliothek: Rechteckübergreifendes«, erweiterte Grafikanwendung. Dort wurde auch ein klassengebundener Zähler eingeführt, der nun im Objektkonstruktor hochgezählt wird. Der parameterlose Konstruktor reicht die Initialisierung an einen überladenen weiter. Durch Verwendung der in Abschnitt 3.3.7, »Grafikbibliothek: Zugriffsmethoden auf die Größe des Rechtecks«, definierten Methode Größe() ist ein Test auf negative Abmaße integriert. Da dieser eine Ausnahme erzeugen kann, wird der Objektzähler erst danach inkrementiert.
'...\Klassendesign\Graphik\Konstruktoren.vb |
Option Explicit On
Namespace Klassendesign
Partial Public Class Rechteck
Sub New()
Me.New(1, 1)
End Sub
Sub New(ByVal a As Double, Optional ByVal b As Double = 1)
Größe(a, b)
anzahl += 1
End Sub
End Class
End Namespace
Zum Test definieren wir ein paar Rechtecke und geben deren Größe aus. Außerdem wird in der klassengebundenen und daher über den Klassennamen aufgerufenen Methode Erzeugt() die Anzahl erzeugter Rechtecke ausgegeben.
'...\Klassendesign\Zeichner\Konstruktoren.vb |
Option Explicit On
Namespace Klassendesign
Partial Class Zeichner
Sub Konstruktoren()
Dim kr As Rechteck
kr = New Rechteck() : kr.Größe()
kr = New Rechteck(7) : kr.Größe()
kr = New Rechteck(7, 8) : kr.Größe()
Rechteck.Erzeugt()
End Sub
End Class
End Namespace
Zur Kontrolle sehen Sie hier die Ausgabe der Methode Konstruktoren():
Dimension des Rechtecks: 1x1
Dimension des Rechtecks: 7x1
Dimension des Rechtecks: 7x8
Anzahl der erzeugten Rechtecke: 3
Die nächste Erweiterung erfolgt in Abschnitt 3.6.5, »Grafikbibliothek: Konstanten des Rechtecks«.
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.