3.15 Schnittstellen: Interface und Implements
Kommen wir nun zur zweiten Art der Vererbung in Visual Basic. Sie legt nur eine Schnittstelle fest, ist also eine reine Form ohne Inhalt. Aus der Inhaltslosigkeit ergibt sich:
Von einer Schnittstelle können keine Objekte erzeugt werden. |
Diese rein formelle Festlegung wird durch einen neuen Datentyp kenntlich gemacht, der Schnittstelle (engl. Interface) genannt wird. Durch sie wird ein Vertrag angeboten, der nur in einer Klasse erfüllt werden kann. Dabei legt die Klasse durch Deklaration fest, dass und wie sie ihn erfüllt.
Eine Schnittstelle ist im Wesentlichen wie eine Klasse aufgebaut, darf aber ausschließlich die in der folgenden Syntax gelisteten Elemente und Modifikatoren enthalten (die letzte Möglichkeit hat eigentlich nichts mit der Idee von Schnittstellen zu tun, siehe Abschnitt 3.8, »Innere Klassen«). Optionale Teile sind in eckige Klammern gesetzt und Alternativen durch einen senkrechten Strich | getrennt. Kursiv gesetzte Teile müssen Sie Ihren Bedürfnissen anpassen. Wie üblich fasst ein Unterstrich zwei Zeilen zu einer logischen Zeile zusammen. Die Typen-Spezifikation wird im Abschnitt 4.4, »Generisches«, erläutert.
[Friend|Public] Interface IName[Typen] [[Shadows|Overloads] Sub name[Typen]([<Parameter>])] [[Shadows|Overloads] Function name[Typen]([<Parameter>]) As Typ] [[[Shadows|Overloads][Default][ReadOnly|WriteOnly] _ Property name([<Indizes>]) As Typ] [[Shadows] Event name As Delegate-Typ] [[[Shadows] Event name([<Parameter>])] [<Typ (zum Beispiel Delegate/Klasse/Schnittstelle)>] End Interface |
Dabei möchte ich auf folgende Besonderheiten hinweisen:
- Der Name der Schnittstelle sollte mit dem Buchstaben I beginnen, um sie leichter von Klassen zu unterscheiden und damit einige später zu besprechende Konzepte leichter zu realisieren sind (siehe zum Beispiel Erweiterungsattribute in Abschnitt 4.8, »Attribute«).
- Schnittstellen enthalten nie Anweisungen oder End-Konstrukte.
- Schnittstellenmitglieder haben nie Sichtbarkeitsmodifikatoren, sie sind implizit öffentlich.
- Schnittstellenmitglieder sind nie mit Shared klassengebunden.
- Schnittstellen enthalten nie Felder oder Operatoren.
- Schnittstellen dürfen leer sein (Markerschnittstellen).
- Eine Aufteilung mit Partial ist nicht erlaubt.
- Typdefinitionen in einer Schnittstelle stellen keinen von einer Klasse zu konkretisierenden Vertrag dar, sondern sind »einfach« geschachtelte Typen (siehe Abschnitt 3.8, »Innere Klassen«).
Einige dieser Beschränkungen sind notwendigerweise mit dem Konzept einer Schnittstelle verbunden, wie zum Beispiel das Fehlen von Anweisungen. Andere sind einfach so in der Sprachdefinition von Visual Basic festgelegt.
Kommen wir nun dazu, wie eine Klasse von einer Schnittstelle Gebrauch macht. Dazu deklariert sie mittels Implements, dass die Klasse die Schnittstelle konkretisiert. Außerdem wird an jedem Klassenmitglied vermerkt, wenn es ein Schnittstellenmitglied implementiert. In der folgenden Syntax sind optionale Teile in eckige Klammern gesetzt und kursiv gesetzte Teile müssen Sie Ihren Bedürfnissen anpassen. Wie üblich fasst ein Unterstrich zwei Zeilen zu einer logischen Zeile zusammen. Die Typen-Spezifikation wird im Abschnitt 4.4, »Generisches«, erläutert.
Class Klasse Implements IName [<Modifikatoren>] Sub Name[Typen]([<Parameter>]) Implements IName.Name[, ...] [<Anweisungen>] End Sub [<Modifikatoren>] Function Name[Typen]([<Parameter>]) As Typ _ Implements IName.Name[, ...] [<Anweisungen>] End Function [<Modifikatoren>][Default][ReadOnly|WriteOnly] _ Property Name ([<Indizes>]) As Typ Implements IName.Name[, ...] [<Sichtbarkeit>] Get [<Anweisungen>] End Get [<Sichtbarkeit>] Set(wert As Typ) [<Anweisungen>] End Set End Property [<Sichtbarkeit>][Shadows] Event name As Delegate-Typ _ Implements IName.Name[, ...] [<Sichtbarkeit>][Shadows] Event name([<Parameter>]) _ Implements IName.Name[, ...] End Class |
Als Modifikatoren sind weniger erlaubt als bei allgemeinen Methoden und Eigenschaften (siehe Tabelle 3.26).
Art | Beschreibung |
Sichtbarkeit |
Grad der Öffentlichkeit (siehe Abschnitt 3.2, »Kapselung«) |
Redefinition |
Art des Ersatzes oder Zwangs zu einer Definition (siehe Abschnitt 3.13.4, »Modifikation: Shadows und Overloads (Overrides)«, Abschnitt 3.13.5, »Abstrakte Klassen: MustInherit und MustOverride«, Abschnitt 3.14.1, »Virtuelle Methoden: Overridable und Overrides« und Abschnitt 3.3.4, »Überladung«) ohne: NotOverridable. |
Die Implementation einer Schnittstelle erweitert den Datentyp eines Objekts:
Ein Objekt einer Klasse, die eine Schnittstelle implementiert, ist (unter anderem) vom Typ der implementierten Schnittstelle. |
Auch bei der Implementation einer Schnittstelle gibt es ein paar Besonderheiten:
- Eine Klasse darf, im Gegensatz zur Vererbung, mehrere Schnittstellen implementieren.
- Ein Klassenmitglied kann beliebig viele Schnittstellenmitglieder zugleich implementieren.
- Es müssen alle Schnittstellenmitglieder implementiert werden (gegebenenfalls über Vererbung).
- Dasselbe Schnittstellenmitglied darf nicht mehrfach implementiert werden.
- Implementierende Klassenmitglieder sind nie mit Shared klassengebunden.
- Externe Funktionen können nie eine Schnittstelle implementieren.
- Namen von Schnittstellenmitgliedern und implementierenden Klassenmitgliedern dürfen unterschiedlich sein.
- Parametertypen in der Schnittstelle und der implementierenden Klasse sind identisch.
- Öffentliche Schnittstellenmitglieder können von privaten Klassenmitgliedern implementiert werden (meiner Meinung nach ein unglückliches Sprachdesign).
Als Beispiel dient der Vertrag der Bürger eines Staates mit den Finanzbehörden. Da alle Steuern zahlen müssen, aber nicht jeder in derselben Art und Weise, bietet es sich an, die Verpflichtung, dass zu zahlen ist, in einer Schnittstelle IBürger zu deklarieren und erst in den implementierenden Klassen festzulegen, wie bezahlt wird. Dazu deklarieren die Klassen Bauer und Händler mit Implements IBürger, dass sie die Schnittstelle implementieren. Zusätzlich wird die implementierende Methode mit IBürger.SteuernZahlen markiert. In einer Klasse, die mehrere passende Methodensignaturen bereitstellt, ist dies logisch notwendig und wird von Visual Basic zwecks Konsistenz immer gefordert. Die verschiedenen Implementierungen reflektieren die unterschiedlichen Arten, Steuern zu zahlen. Die Methode Test() schließlich greift auf die verschiedenen Steuerzahler über den Typ der Schnittstelle zu.
Hinweis |
Um die Beispiele einfach zu halten, enthalten sie nur je ein Schnittstellenmitglied. Im Allgemeinen kann eine Schnittstelle viele Mitglieder haben, inklusive der überladenen. |
'...\Klassendesign\Schnittstellen\Prinzip.vb |
Option Strict On Namespace Klassendesign Interface IBürger Sub SteuernZahlen() End Interface Class Bauer : Implements IBürger Sub Steuern() Implements IBürger.SteuernZahlen Console.WriteLine("Bauer zahlt in Naturalien.") End Sub End Class Class Händler : Implements IBürger Sub SteuernZahlen() Implements IBürger.SteuernZahlen Console.WriteLine("Händler zahlt mit Geld.") End Sub End Class Module Prinzip Sub Test() For Each z As IBürger In New IBürger() {New Händler(), New Bauer()} z.SteuernZahlen() Next Console.ReadLine() End Sub End Module End Namespace
Die Ausgabe zeigt, wie Händler und Bauer, als IBürger angesprochen, Steuern zahlen:
Händler zahlt mit Geld. Bauer zahlt in Naturalien.
Hinweis |
.NET arbeitet oft mit Schnittstellen, wie zum Beispiel IEnumerable und IDisposable, aber leider nicht ganz konsequent. Zum Beispiel setzt die For Each-Schleife nicht unbedingt IEnumerable voraus, sondern es reicht eine zugängliche Methode der Signatur Function GetEnumerator() As Collections.IEnumerator. |
3.15.1 Benennungen und Parameter
Die Namen der implementierenden Klassenmitglieder werden meist so gewählt wie die der Schnittstellenmitglieder. Dies ist aber nicht zwingend und sehr nützlich, wenn dasselbe Klassenmitglied gleichzeitig verschiedennamige Mitglieder mehrerer Schnittstellen implementieren soll. Das folgende Codefragment zeigt die zwei Schnittstellen ITransporter und IAuto, die für die Eigenschaft der maximalen Zuladung die beiden verschiedenen Namen Nutzlast() und Beladung() wählen. Da dasselbe Fahrzeug dieselbe Zuladung hat, egal ob Sie es als Transporter oder Auto ansehen, werden beide Schnittstellenmethoden in der Klasse LKW in einer einzigen Eigenschaft Zuladung() implementiert. Die Methode Test() zeigt, dass der Typ einer Variablen den Namen der Eigenschaft bestimmt.
'...\Klassendesign\Schnittstellen\Benennungen.vb |
Option Strict On Namespace Klassendesign Interface ITransporter ReadOnly Property Nutzlast() As Integer End Interface Interface IAuto ReadOnly Property Beladung() As Integer End Interface Class LKW : Implements ITransporter, IAuto Public ReadOnly Property Zuladung() As Integer _ Implements IAuto.Beladung, ITransporter.Nutzlast Get Return 1000 End Get End Property End Class Module Benennungen Sub Test() Dim lkw As LKW = New LKW() : Dim trans As ITransporter = lkw Console.WriteLine("Zuladung {0}", lkw.Zuladung) Console.WriteLine("Zuladung {0}", trans.Nutzlast) Console.ReadLine() End Sub End Module End Namespace
Beide Variablen greifen auf dasselbe Objekt zu und liefern dieselbe Last, egal wie sie genannt wird.
Zuladung 1000 Zuladung 1000
Bei der Implementierung mehrerer Ereignisse in demselben Ereignis gibt es noch eine kleine Stolperfalle. Dies ist nur möglich, wenn die Ereignisse identisch dasselbe Delegate verwenden. Da ein Ereignis ohne As-Klausel implizit ein neues Delegate definiert, können also nur solche Ereignisse in einem einzigen Ereignis implementiert werden, die mit einer As-Klausel dasselbe Delegate spezifizieren. Das folgende Codefragment zeigt zwei Schnittstellen, IAusbilder und ITrainer, mit einem Ereignis, das dessen Typ über ein Delegate festlegt und eine Schnittstelle IChorleiter, die den Ereignistyp implizit definiert. Die beiden ersten Ereignisse werden in der Klasse Lehrer in einem einzigen Ereignis implementiert. Dies ist beim Ereignis der dritten Schnittstelle nicht möglich, und es wird in einem zusätzlichen Ereignis implementiert.
'...\Klassendesign\Schnittstellen\Ereignisse.vb |
Option Strict On Namespace Klassendesign Delegate Sub Aufruf() Interface IAusbilder Event Anwesend As Aufruf End Interface Interface ITrainer Event Zählen As Aufruf End Interface Interface IChorleiter Event Stimmen() End Interface Class Lehrer : Implements IAusbilder, ITrainer, IChorleiter Event Appell As Aufruf Implements IAusbilder.Anwesend, ITrainer.Zählen Event Appell2() Implements IChorleiter.Stimmen ... End Class ... End Namespace
Keine Wahl besteht hingegen bei den Typen. So kann zum Beispiel aus einer Eigenschaft einer Schnittstelle nicht eine implementierende Methode in einer Klasse werden. Auch müssen die Typen von Parametern erhalten bleiben, inklusive der Standardwerte optionaler Parameter. Das nächste Beispiel berechnet den Einkommenssteuersatz in Abhängigkeit von den optionalen Einnahmen. Der Standardwert ist so gewählt, dass ohne Einkommensangabe der Höchststeuersatz gilt. Die implementierende Klasse Angestellter muss diesen Standardwert übernehmen. Jeder andere Wert verursacht einen Compilerfehler.
'...\Klassendesign\Schnittstellen\Parameter.vb |
Option Strict On Namespace Klassendesign Interface ISteuerzahler Function Steuersatz(Optional ByVal einkommen As Int32 = 250000) As Int32 End Interface Class Angestellter : Implements ISteuerzahler Function Steuersatz(Optional ByVal einkommen As Int32 = 250000) _ As Int32 Implements ISteuerzahler.Steuersatz If einkommen < 7664 Then Return 0 Dim d As Integer = 12740 – 7664 If einkommen < 12740 Then Return 15 + (einkommen – 7664) / d * 9 d = 52152 – 12740 If einkommen < 52152 Then Return 24 + (einkommen – 12740) / d * 18 If einkommen < 250000 Then Return 42 Return 45 End Function End Class Module Parameter Sub Test() Dim an As Angestellter = New Angestellter() For Each ein As Integer In New Integer() {7000, 10000, 20000, 40000} Console.WriteLine("{1}% Steuern bei {0}", ein, an.Steuersatz(ein)) Next Console.WriteLine("{0}% Steuern ohne Angabe", an.Steuersatz()) Console.ReadLine() End Sub End Module End Namespace
Die Ausgabe bestätigt die korrekte Arbeitsweise:
0% Steuern bei 7000 19% Steuern bei 10000 27% Steuern bei 20000 36% Steuern bei 40000 45% Steuern ohne Angabe
Hinweis |
Die Parametertypen (inklusive ByVal/ByRef) und gegebenenfalls Standardwerte in der Schnittstelle und der implementierenden Klasse müssen identisch sein. Selbst implizite (erweiternde) Typumwandlungen werden nicht berücksichtigt. |
3.15.2 Schnittstellen und Vererbung
Geerbte Schnittstellenimplementierungen
Ist eine Schnittstelle einmal implementiert, vererbt sich diese Tatsache auch auf alle Kindklassen, so als sei die Schnittstelle in der Kindklasse implementiert worden. Das folgende Beispiel verwendet die folgende Vererbungshierarchie:
IHaus +-Haus +-Bude +-Villa
Die Schnittstelle IHaus hat zwei Methoden, die in der Klasse Haus implementiert werden. Dann wird von Haus die Klasse Bude abgeleitet, ohne Haus zu ändern oder zu ergänzen (die Angabe von Implements IHaus ist optional). Dagegen wird in der Klasse Villa eine der beiden Methoden neu implementiert. Dies ersetzt nur diese eine Signatur, ohne die andere zu beeinflussen. In der Methode Test() werden eine Villa und eine Bude über die Basisklassenreferenz Haus genutzt.
'...\Klassendesign\Schnittstellen\Vererbung.vb |
Option Strict On Namespace Klassendesign Interface IHaus Overloads Sub Wand(ByVal material As String) Overloads Sub Wand(ByVal kostenrahmen As Double) End Interface Class Haus : Implements IHaus Overridable Sub Wand(ByVal material As String) Implements IHaus.Wand Console.WriteLine("Wanddicke Haus " & If(material = "Beton", 20, 25)) End Sub Overridable Sub Wand(ByVal kostenrahmen As Double) Implements IHaus.Wand Console.Write("Materialwahl Haus : ") Wand(If(kostenrahmen <= 15, "Beton", "Stein")) End Sub End Class Class Bude : Inherits Haus : End Class Class Villa : Inherits Haus : Implements IHaus Overrides Sub Wand(ByVal kostenrahmen As Double) Implements IHaus.Wand Console.Write("Materialwahl Villa: ") Wand(If(kostenrahmen <= 10, "Beton", "Stein")) End Sub End Class Module Vererbung Sub Test() Dim bude As IHaus = New Bude() : bude.Wand(15) Dim villa As IHaus = New Villa() : villa.Wand(15) Console.ReadLine() End Sub End Module End Namespace
Der erste Aufruf von Wand() nutzt die Implementation der Klasse Haus. Die zweite Ausgabe zeigt, dass die neue Implementation von Villa verwendet wird.
Materialwahl Haus : Wanddicke Haus 20 Materialwahl Villa: Wanddicke Haus 25
Hinweis |
Ist eine Schnittstelle implementiert, dürfen alle Kindklassen mit Implements diese Tatsache erneut deklarieren. Dabei kann ein Teil oder die ganze Schnittstelle neu implementiert werden. |
Basistyp
Die Aussage, dass alle Klassen als Wurzel die Klasse Object haben, erstreckt sich nicht auf Schnittstellen. Das nächste Codefragment testet auf den Elterntyp einer Schnittstelle:
'...\Klassendesign\Schnittstellen\Wurzel.vb |
Option Strict On Namespace Klassendesign Interface IWurzel : End Interface Module Wurzel Sub Test() If GetType(IWurzel).BaseType Is Nothing Then _ Console.WriteLine("Object ist nicht Basis!") Console.ReadLine() End Sub End Module End Namespace
Wie die Ausgabe zeigt, hat die Schnittstelle nicht Object als Basis:
Object ist nicht Basis!
Trotzdem können Schnittstellenreferenzen auf Mitglieder der Klasse Object zugreifen und Erweiterungsmethoden vom Typ Object nutzen (siehe Abschnitt 4.8.4, »Klassenerweiterungen: Extension«), da eine implizite Konvertierung existiert. Dazu zeigt das nächste Beispiel den Zugriff auf GetType() der Klasse Object:
'...\Klassendesign\Schnittstellen\Object.vb |
Option Strict On Namespace Klassendesign Interface IObject : End Interface Class Objekt : Implements IObject : End Class Module KonvertierungZuObject Sub Test() Dim ref As IObject = New Objekt() Console.WriteLine("Typ: {0}", ref.GetType().Name) Console.ReadLine() End Sub End Module End Namespace
Der korrekte Typ wird ausgegeben:
Typ: Objekt
3.15.3 Schnittstellenvererbung
Neben Klassen können auch Schnittstellen voneinander erben. Im Gegensatz zu Klassen gilt:
Eine Schnittstelle darf mehrere Elternschnittstellen haben, aber keine Elternklasse. |
Dabei werden alle Mitglieder aller Elternklassen übernommen, sodass bei der Implementation der Kindschnittstelle auch alle Mitglieder aller Elternklassen berücksichtigt werden müssen. Sonst wäre ja die Kindschnittstelle nicht vollständig implementiert. Im folgenden Beispiel erbt die Schnittstelle IZitrusfrucht von den Schnittstellen IObst und ILebensmittel. Die Klasse Orange deklariert Implements IZitrusfrucht und muss daher alle drei Methoden der Schnittstelle implementieren, wobei die Methoden Vitamine() und Kalorien() über Vererbung in die Schnittstelle gelangt sind.
'...\Klassendesign\Schnittstellen\Schnittstellenvererbung.vb |
Option Strict On Namespace Klassendesign Interface IObst Sub Vitamine() End Interface Interface ILebensmittel Sub Kalorien() End Interface Interface IZitrusfrucht : Inherits IObst, ILebensmittel Sub Schälen() End Interface Class Orange : Implements IZitrusfrucht Sub Vitamine() Implements IObst.Vitamine Console.WriteLine("Orange hat Vitamin C.") End Sub Sub Schälen() Implements IZitrusfrucht.Schälen Console.WriteLine("Orange wird geschält mit einem Messer.") End Sub Sub Kalorien() Implements ILebensmittel.Kalorien Console.WriteLine("Orange hat weniger Kalorien als Schokolade.") End Sub End Class Module Schnittstellenvererbung Sub Test() Dim orange As IZitrusfrucht = New Orange() orange.Vitamine() : orange.Kalorien() : orange.Schälen() Console.ReadLine() End Sub End Module End Namespace
Über die Schnittstellenreferenz sind alle drei implementierten Methoden erreichbar:
Orange hat Vitamin C. Orange hat weniger Kalorien als Schokolade. Orange wird geschält mit einem Messer.
Hinweis |
Wie in Abschnitt 3.13.2, »Sichtbarkeitsmodifikatoren«, beschrieben wird, darf eine Kindschnittstelle nicht sichtbarer sein als ihre Eltern. |
Mehrfachvererbung
Bei der Vererbung von Schnittstellen werden nur Signaturen vererbt. Wird nun durch Mehrfachvererbung auf verschiedenen Wegen dieselbe Signatur vererbt, ist sie immer noch identisch dieselbe und findet sich in der Kindschnittstelle auch nur einmalig wieder. Das folgende Beispiel stellt die Methode Schneiden() in ITaschenmesser durch Vererbung sowohl über IMesser als auch über ISäge zur Verfügung. Dennoch reicht es, in der Klasse Taschenmesser die Methode nur einmalig zu implementieren. Statt Implements ITaschenmesser.Schneiden darf daher auch Implements IMesser.Schneiden oder Implements ISäge.Schneiden verwendet werden. Da alle drei Möglichkeiten dieselbe Methode implementieren, darf auch nur eine der drei vorkommen.
'...\Klassendesign\Schnittstellen\Mehrfachvererbung.vb |
Option Strict On Namespace Klassendesign Interface ISchnittwerkzeug : Sub Schneiden() : End Interface Interface IMesser : Inherits ISchnittwerkzeug : End Interface Interface ISäge : Inherits ISchnittwerkzeug : End Interface Interface ITaschenmesser : Inherits IMesser, ISäge : End Interface Class Taschenmesser : Implements ITaschenmesser Sub Schneiden() Implements ITaschenmesser.Schneiden Console.WriteLine("Mit Taschenmesser Schneiden") End Sub End Class Module Mehrfachvererbung Sub Test() Dim taschenmesser As ITaschenmesser = New Taschenmesser() taschenmesser.Schneiden() Console.ReadLine() End Sub End Module End Namespace
Wenn eine Schnittstelle mehrere Elternschnittstellen haben kann, tauchen Probleme auf, die bei der Einfachvererbung der Klassen nicht auftreten können. Wenn eine Schnittstelle identische Signaturen aus verschiedenen Elternschnittstellen übernimmt, so hat sie zwei (mehrdeutige) Mitglieder, die gleich lauten. Beim Zugriff würde ein Konflikt entstehen, da nicht entschieden werden kann, welche denn nun gemeint ist. Daher muss über die passende Basisschnittstellenreferenz zugegriffen werden. Das folgende Codefragment definiert eine Schnittstelle IStereoanlage, die sowohl von IRadio als auch von ICompactDisc die Methodensignatur Musik() erbt. Die Klasse Musikanlage implementiert die gleichlautenden Signaturen in verschiedenen Methoden. In der Realität ist der Begriff von Musik ja schließlich auch recht unterschiedlich. In der Methode Test() kommt es nun zum Zugriff auf die Methode Musik() über die mehrdeutige Schnittstellenreferenz. Der direkte Zugriffsversuch wird vom Compiler zurückgewiesen und ist daher auskommentiert. Erst durch eine Typumwandlung, die eindeutig klarmacht, auf welche Methode zugegriffen werden soll, können die beiden Methoden verwendet werden.
'...\Klassendesign\Schnittstellen\Mehrdeutig.vb |
Option Strict On Namespace Klassendesign Interface IRadio : Sub Musik() : End Interface Interface ICompactDisc : Sub Musik() : End Interface Interface IStereoanlage : Inherits IRadio, ICompactDisc : End Interface Class Musikanlage : Implements IStereoanlage Sub Hifi() Implements ICompactDisc.Musik Console.WriteLine("'s Brent, briderlech, 's brent ...") End Sub Sub Schepper() Implements IRadio.Musik Console.WriteLine("All we like sheep ...") End Sub End Class Module Mehrdeutig Sub Test() Dim anlage As IStereoanlage = New Musikanlage() 'anlage.Musik() 'Compilerfehler: mehrdeutig!! CType(anlage, IRadio).Musik() CType(anlage, ICompactDisc).Musik() Console.ReadLine() End Sub End Module End Namespace
Die verschiedenen Arten der Musik machen sich in der Ausgabe bemerkbar:
All we like sheep ... 's Brent, briderlech, 's brent ...
3.15.4 Schnittstelle oder abstrakte Klasse?
Die Konzepte einer Schnittstelle und abstrakten Klasse sind sehr ähnlich. Es gibt keine allgemeine Empfehlung, wann welche benutzt werden sollte. Die Tabelle 3.27 soll eine Hilfestellung für die Entscheidung sein. Ist keine eindeutige Entscheidung möglich, wählen Sie eine Möglichkeit und experimentieren damit. In der Programmierpraxis werden Sie dann »Ihren« Weg finden.
Art | Schnittstelle | abstrakte Klasse |
Vererbung |
mehrfach |
einfach |
Implementation |
nie, reiner Vertrag |
darf (oft allgemeine Funktionalität) |
Abstraktion |
vollständig |
von gar nicht bis vollständig |
Änderungen nach Freigabe |
unbedingt vermeiden |
Signaturen und Rückgabetypen unbedingt unverändert, im implementierten Teil kann notfalls geändert werden. |
typisches Einsatzgebiet |
Toolklassen, zum Beispiel zum Sortieren und Selektieren |
an sich konkrete Klassen, in denen ein Teil erst in Kindklassen festgelegt wird, zum Beispiel Drucker mit allgemeiner Warteschlange |
3.15.5 Grafikbibliothek: Flächen
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.14.7, »Grafikbibliothek: Objektsammlungen«, erweiterte Grafikanwendung. Dazu werden zwei Schnittstellen eingeführt, die den Namen und den Umfang eines Umrisses beschreiben:
'...\Klassendesign\Graphik\Schnittstellen.vb |
Option Explicit On Namespace Klassendesign Public Interface IFigur Function Name() As String End Interface Public Interface IUmriss : Inherits IFigur ReadOnly Property Umfang() As Double End Interface ... End Namespace
Im Rechteck werden die Schnittstellen implementiert. Der Name soll polymorph sein und wird mit Overridable gekennzeichnet sowie im Quadrat überschrieben.
'...\Klassendesign\Graphik\Schnittstellen.vb |
Option Explicit On Namespace Klassendesign ... Partial Public Class Rechteck : Inherits Vieleck : Implements IUmriss Public Overridable Function Name() As String Implements IFigur.Name Return "Rechteck" End Function Public ReadOnly Property Umfang() As Double Implements IUmriss.Umfang Get Return 2 * a + 2 * b End Get End Property End Class Partial Public NotInheritable Class Quadrat : Inherits Rechteck Public Overrides Function Name() As String Return "Quadrat" End Function End Class End Namespace
Zum Test werden ein paar Umrisse erzeugt und über den Schnittstellentyp angesprochen:
'...\Klassendesign\Zeichner\Scnittstellen.vb |
Option Explicit On
Namespace Klassendesign
Partial Class Zeichner
Sub Schnittstellen()
Dim umrisse() As IUmriss = {New Quadrat(7), New Rechteck(4, 6)}
For Each u As IUmriss In umrisse
Console.WriteLine(u.Name() & " mit " & u.Umfang & " Umfang")
Next
End Sub
End Class
End Namespace
Zur Kontrolle sehen Sie hier die Ausgabe der Methode Schnittstellen():
Quadrat mit 28 Umfang Rechteck mit 20 Umfang
Die nächste Erweiterung erfolgt in Abschnitt 3.2.6, »Grafikbibliothek: private Größe 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.