3.8 Innere Klassen
Bisher haben wir uns mit »flachen« Klassen beschäftigt. Unter flach verstehe ich hier eine Klasse, die keine anderen Klassen enthält. In diesem Abschnitt betrachten wir den Fall, dass eine Klasse eine andere enthält. Auch die später im Buch beschriebenen Datentypen können in eine Klasse eingebettet sein und folgen den hier vorgestellten Konzepten. Lediglich der Typ Module kann nicht innerhalb eines anderen Datentyps stehen. Umgekehrt können fast alle Datentypen innere Datentypen enthalten (einzige Ausnahme: siehe Abschnitt 4.3, »Enumerationen«).
Klassen und andere Datentypen werden geschachtelt, um eine logische Beziehung zu der umgebenden Klasse oder einem anderen Typ zu unterstreichen. Es wird keine Verbindung zwischen Objekten geschaffen, lediglich die geschachtelten Datentypen hängen zusammen. So kann zum Beispiel eine Klasse Rad einmal in einer Auto-Klasse und eine andere davon unabhängig in einer Kutsche-Klasse stecken. Beide Räder sind recht unterschiedlich, und es ist zur Unterscheidung sinnvoll, den logischen Bezug zum Fahrzeugtyp herzustellen. Die Deklaration erfolgt analog zu einer ungeschachtelten Klasse. In der folgenden Syntax sind die Zeilenvorschübe zwingend, während optionale Teile in eckige Klammern gesetzt sind. Die Namen Außen und Innen sowie andere kursive Teile dürfen natürlich beliebig umbenannt werden.
[<Modifikatoren>] Class Außen ... [<Modifikatoren>] Class Innen [<Effekte>] [<Mitglieder>] End Class ... End Class |
Hinweis |
Statt Class können auch andere Datentypen stehen, die innere Typen erlauben. |
Es gibt mehr Modifikatoren als in einer Klasse auf oberster Ebene. Wie Tabelle 3.11 zeigt, sind insbesondere bei der Sichtbarkeit alle für andere Klassenmitglieder erlaubten Spezifikationen möglich und nicht nur Friend und Public.
Art | Beschreibung |
Sichtbarkeit |
Grad der Öffentlichkeit (siehe Abschnitt 3.2, »Kapselung«) |
Redefinition |
Art des Ersatzes oder Zwangs zu einer Definition (siehe Abschnitt 3.13, »Vererbung«) |
Aufteilung |
Definition an verschiedenen Orten (siehe Abschnitt 3.1.11, »Aufteilung der Definition mit Partial«) |
Hinweis |
Innere Datentypen sind implizit an den umgebenden Typ gebunden und weder brauchen noch dürfen sie mit Shared gekennzeichnet werden. |
Die erlaubten Effekte sind dieselben wie für andere Klassen (siehe Tabelle 3.12). Bei anderen Datentypen als Klassen sind nicht immer alle erlaubt.
Art | Beschreibung |
Implementation |
Erfüllung einer Schnittstelle (siehe Abschnitt 3.15, »Schnittstellen: Interface und Implements«) |
Vererbung |
Aufbau auf anderer Klasse (siehe Abschnitt 3.13.1, »Klassenbeziehung durch Inherits«) |
Der Zugriff auf einen inneren Datentyp erfolgt einfach über die Qualifikation mit dem Namen der äußeren Klasse. Beim Zugriff ist formell nicht zu unterscheiden, ob die Klasse Teil einer anderen ist oder in einem gleichlautenden Namensraum steckt (die Klassennamen sind beliebig).
<äußere Klasse>.<innere Klasse> |
Das folgende Codefragment zeigt exemplarisch die Erzeugung einer Variablen vom Typ der inneren Klasse:
Dim var As Außen.Innen = New Außen.Innen()
Schauen wir uns nun das Ganze in einem Beispiel an. Es definiert eine Klasse Auto, in der eine Klasse Motor eingebettet ist.
'...\Klassendesign\InnereKlassen\Prinzip.vb |
Option Explicit On Namespace Klassendesign Class Auto Class Motor Sub starten() Console.WriteLine("Motor gestartet!") End Sub End Class End Class Module Prinzip Sub Test() Dim motor As Auto.Motor = New Auto.Motor() motor.starten() Console.ReadLine() End Sub End Module End Namespace
Die Ausgabe bestätigt die korrekte Arbeitsweise:
Motor gestartet!
Außer der Objekterzeugung mit einem qualifizierten Namen ist nichts Besonderes an der Verwendung einer inneren Klasse.
3.8.1 Beziehung zur äußeren Klasse
Die Einbettung einer Klasse in eine andere bezieht sich auch auf den Zugriff auf die sie umgebende Klasse. Die innere Klasse hat vollständigen direkten Zugriff auf alle Mitglieder der äußeren Klasse. Das nächste Codefragment definiert eine Klasse Schüler innerhalb von Schulklasse, die zwei private Felder hat. In der Prozedur Test() wird ein Schüler-Objekt geschaffen, und über die Methoden Auskunft() wird auf die Felder der Klasse Schulklasse zugegriffen. Da die Beziehung der Klassen keine Objektbeziehung darstellt, wird der zweiten Methode ein Schulklasse-Objekt übergeben, das die Objektbeziehung herstellt.
'...\Klassendesign\InnereKlassen\InnenNachAussen.vb |
Option Explicit On Namespace Klassendesign Class Schulklasse Private Shared name As String = "Crey" Private spitzname As String = "Schnauz" Class Schüler Sub Auskunft() Console.WriteLine("Lehrer: {0}", name) End Sub Sub Auskunft(ByVal klasse As Schulklasse) Console.WriteLine("Genannt: {0}", klasse.spitzname) End Sub End Class End Class Module Hinein Sub Test() Dim pfeiffer As Schulklasse.Schüler = New Schulklasse.Schüler() pfeiffer.Auskunft() pfeiffer.Auskunft(New Schulklasse()) Console.ReadLine() End Sub End Module End Namespace
Dass die auszugebenden Felder privat sind, behindert die innere Klasse nicht beim Zugriff.
Lehrer: Crey Genannt: Schnauz
In der anderen Richtung, das heißt von außen nach innen, bestehen keine Besonderheiten beim Zugriff. Daher wird das folgende Codefragment vom Compiler zurückgewiesen:
Class Schulklasse
Sub Auskunft()
Dim pfeiffer As Schüler = New Schüler()
Console.WriteLine("Pfeiffer: {0}", pfeiffer.zustand) 'Fehler!!
End Sub
Class Schüler
Private zustand As String = "verliebt"
End Class
End Class
Da über den Quelltext keine Objektbeziehung zwischen innerer und äußerer Klasse hergestellt wird, bietet es sich an, diese über den Konstruktor der inneren Klasse zu erzwingen. Das nächste Codefragment erzwingt damit die Beziehung zwischen dem Flugzeug und der Turbine, indem nur ein parametrisierter Konstruktor existiert, der ein Flugzeug-Objekt entgegennimmt.
'...\Klassendesign\InnereKlassen\Objektbeziehung.vb |
Option Explicit On Namespace Klassendesign Class Flugzeug Private name As String Sub New(ByVal name As String) Me.name = name End Sub Class Turbine Private flugzeug As Flugzeug Private nr As Integer Sub New(ByVal flugzeug As Flugzeug, ByVal nr As Int32) Me.flugzeug = flugzeug : Me.nr = nr End Sub Sub Auskunft() Console.WriteLine("Turbine {0} in Flugzeug {1}", nr, flugzeug.name) End Sub End Class End Class Module Beziehung Sub Test() Dim flug As Flugzeug = New Flugzeug("A380") Dim turb As Flugzeug.Turbine turb = New Flugzeug.Turbine(flug, 3) turb.Auskunft() Console.ReadLine() End Sub End Module End Namespace
Die Objektbeziehung kann in der Ausgabe genutzt werden:
Turbine 3 in Flugzeug A380
3.8.2 Sichtbarkeit
Eine innere Klasse ist bezüglich der Sichtbarkeit ein normales Klassenmitglied. Dies bedeutet, dass sie auch privat sein kann. In Zusammenhang mit dem Geheimnisprinzip bedeutet dies, dass auch kein Objekt vom Typ der inneren Klasse außerhalb der Klasse existieren kann. Daher wird das folgende Codefragment als ungültig zurückgewiesen:
Class Firma Function Interna() As Struktur 'verboten!! Return New Struktur End Function Private Class Struktur Partial Private Sub Anlassen() End Sub End Class End Class
3.8.3 Grafikbibliothek: Position 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.7.9, »Grafikbibliothek: Eigenschaften des Rechtecks«, erweiterte Grafikanwendung. Dem Rechteck wird eine Position hinzugefügt. Sie repräsentiert die linke obere Ecke. Die Motivation, dies in einer inneren Klasse und nicht als Eigenschaft zu machen, sind die Methoden der inneren Klasse zum Bewegen des Rechtecks und zur Ausgabe der absoluten Abmessungen.
'...\Klassendesign\Graphik\InnereKlassen.vb |
Option Explicit On Namespace Klassendesign Partial Public Class Rechteck Class Position Dim rechteck As Rechteck Dim x, y As Double Sub New(ByVal rechteck As Rechteck) Me.rechteck = rechteck End Sub Sub Bewegen(ByVal x As Double, ByVal y As Double) Me.x = x : Me.y = y End Sub Sub Abmessungen() Console.WriteLine("Rechteck {0}x{1} – {2}x{3}", _ x, y, x + rechteck.a, y + rechteck.b) End Sub End Class End Class End Namespace
Zuerst wird ein neues Rechteck erzeugt und dem Konstruktor der Position übergeben. Dann wird das Rechteck bewegt und die Abmessungen werden ausgegeben.
'...\Klassendesign\Zeichner\InnereKlassen.vb |
Option Explicit On Namespace Klassendesign Partial Class Zeichner Sub InnereKlassen() Dim rechteck As New Rechteck(7, 8) Dim position As New Rechteck.Position(rechteck) position.Bewegen(100, 200) position.Abmessungen() End Sub End Class End Namespace
Zur Kontrolle sehen Sie hier die Ausgabe der Methode InnereKlassen():
Rechteck 100x200 – 107x208
Die nächste Erweiterung erfolgt in Abschnitt 3.9.7, »Grafikbibliothek: Vergleich von Rechtecken«.
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.