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>]) [<Anweisungen>] End Sub |
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>]) MyClass.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.