C# Mehrfachvererbung mit Interfaces?

4 Antworten

Der Vorteil ist:

Hat man eine Methode, die einen Parameter Fahrzeug vom Typ Amphibienfahrzeug hat, so kann man - beim Aufruf dieser Methode - für den aktuellen Parameter Fahrzeug ein Landfahrzeug oder auch ein Wasserfahrzeug einsetzen.

FragerHH 
Fragesteller
 22.03.2016, 13:25

Ja, OK... aber was hat das mit Mehrfachvererbung zu tun ? Wäre das nicht eher so was wie Polymorphie ?

0
grtgrt  22.03.2016, 13:37
@FragerHH

Es hat insofern was mit Mehrfachvererbung zu tun, als das, was man mit dieser Konstruktion ereichen kann, eben dem entspricht, was man in C++ erreicht, wenn man dort die Klasse Amphibienfahrzeug sowohl von Landfahrzeug als auch von Wasserfahrzeug erben lässt.

Den Erfindern von Java war das klar, und so haben sie sich dafür entschieden, in Java eben nur Interfaces (aber nicht auch Mehrfachvererbung) zu unterstützen. 

Eine Sprache, die Interfaces unterstützt, braucht keine Mehrfachvererbung. Die ersten Versionen von C++ kannten noch keine Interfaces.

Prinzipiell aber hast du recht: Man kann hier auch von Polymorphie sprechen.

2
FragerHH 
Fragesteller
 22.03.2016, 14:16
@grtgrt

Ah, OK. Danke.

Es scheint dann so zu sein, dass das Ziel bei einer Mehrfachvererbung es primär nicht ist, möglichst viel Code von den Klassen zu erben - sondern eher einen neuen Typ (Amphibienfahrzeug) zu schaffen, der die Eigenschaften beider Klassen hat ?

1

Eine Klasse identifiziert sich neben ihren Daten auch durch ihr Verhalten. Das Verhalten, also die Aufgabe der Klasse, kann durch die Daten beeinflusst und verändert werden.

Eine Schnittstelle hingegen definiert nur ein Verhalten - also Was kann die Klasse, die diese Schnittstelle implementiert. Das Wie entscheidet allerdings die Klasse selbst.

In deinem Beispiel könnte die Methode "Fahren" über die Schnittstelle implementiert werden, die ihrerseits im konkreten Objekt darüber entscheidet, wie das Objekt fahren kann. Im Wasser wird es ja kaum sinnvoll sein die Radachsen anzutreiben.

Anderes Beispiel: Wenn du diverse Fahrzeuge hast, dann könnte theoretisch auch jedes dieser Fahrzeuge ein Einsatzfahrzeug mit Blaulicht und Martinshorn sein. Da würde sich eine Schnittstelle anbieten, die Methoden zum Ein- und Ausschalten des Blaulichts und des Martinshorn definiert (also was kann das Fahrzeug dadurch). Das Fahrzeug selbst (die Klasse) entscheidet dann darüber, wie das Blaulicht aussehen soll; wo es sein soll; wie hell es sein soll; mit welcher Frequenz es leuchten soll, ... und so weiter.

Hallo,

Wurde hier inzwischen ja schon ein paar mal erklärt, aber mir zumindest hat es früher geholfen, möglichst viele verschiedene Erklärungen zu hören, also hier mal mein Ansatz:

Schnittstellen dienen, so ist mir das Licht aufgegangen, der Gruppierung - wenn du dich weiter mit C#-Programmierung beschäftigst, wirst du vielleicht folgendes Szenario oder ein ähnliches erleben: Du möchtest für einen Aquarienrundgang eine Liste (oder ein Array) erstellen, worin alle möglichen Wasserfahrzeuge und Taucher zusammengefasst sind. Aber das geht ja nicht, weil man nicht einfach zwei Typen angeben kann (FALSCHER Pseudocode:

var x = new List<Wasserfahrzeug&&Taucher>();
  // Geht nicht - so eine Syntax gibt es nicht.
  // List<Wasserfahrzeug, Taucher> etc. führen auch nicht zum Ziel.

falls dir diese Syntax noch nichts sagt, entspräche das vlt.:
Wasserfahrzeug[]&&Taucher[] x;
  // was der Compiler jedoch auch nicht erkennt.

)

Daher fragt man sich nun: Warum eigentlich gerade Wasserfahrzeuge und Taucher? Antwort: Die Gemeinsamkeit dieser Typen besteht in ihrer Fähigkeit, sich unter Wasser fortzubewegen, bzw. in ihrer Wasserfestigkeit. Und diese Gemeinsamkeit definieren wir:

interface ITauchfaehig
{
   void BewegenDurchWasser(int x, int y);
     // Methode, die Parameter wären z. B. die Koordinaten
}

Nun muss diese Schnittstelle nur noch von allen Klassen, die "tauchfähig" sind, implementiert werden. Wir fügen zu ihren Signaturen hinzu:

class Wasserfahrzeug : Fahrzeug, ITauchfaehig

bzw.

class Taucher : Mensch, ITauchfaehig

Nun muss man nur noch in diesen Klassen die Versionen von BewegenDurchWasser() schreiben.

Konkret zu deiner Frage also:

Wenn du die Schnittstellen ILandfahrzeug und IWasserfahrzeug hast (anders als in meinem Beispiel oben!) sowie die diese implementierende Klasse Amphibienfahrzeug, mag das erstmal ziemlich sinnlos klingen ... aber wenn du dann noch die Klasse Automobil erstellst, die nur von ILandfahrzeug erbt, oder die Klasse UBoot : IWasserfahrzeug, zahlt sich das bereits aus: Dann kannst du nämlich einfach für die jeweilige Reise die richtigen Fahrzeuge bereitstellen, nämlich z. B. alle vom Typ IWasserfahrzeug für eine Überseereise.

Ich hoffe, ich konnte dir etwas helfen. Frag auch gern noch mal nach :)

MfG,
KnorxThieus (♂)

Woher ich das weiß:Studium / Ausbildung – Studiere Informatik und programmiere aus Leidenschaft
FragerHH 
Fragesteller
 24.03.2016, 06:58

Hallo,

danke für Deine interessante Antwort.

Als ich angefangen habe über Interface zu lesen stand da öfters, dass ein Interface ein "Vertrag" ist den alle Klassen einhalten müssen.

Und dass das zum Beispiel nützlich sei wenn mehrere Entwickler gemeinsam an einem Projekt arbeiten: Man einigt sich vorab über alle Methoden etc. die jede Klasse implementieren muss.

So können sich Entwickler darauf verlassen, dass ihre Kollegen bestimmte Methoden implementieren müssen - weil das Interface das eben so vorgibt.

Na ja, ich dachte mir dann dass das eher eine organisatorische Massnahme ist, denn man könnte so ein Projekt auch ohne Interfaces schaffen, eine technische Notwendigkeit für diesen Zweck Interfaces einzusetzen gibt es nicht.

Dann fand ich noch den Hinweis bzgl. Mehrfachvererbung und dass man das mit Interfaces nachbilden kann.

Und das hatte ich eine zeitlang nicht verstanden, denn ich ging davon aus, dass es bei Vererbung eben genau darum geht so viel Code wie möglich von den Klassen zu übernehmen.

Aber wie soll das gehen wenn man von Interfaces erbt?

Diese haben ja per Definition keinen Code, der vererbt werden kann.

Daher war ich etwas verwirrt...

Inzwischen ist es etwas klarer:
Es geht wohl bei der Mehrfachvererbung über Interfaces um "Gruppierungen" oder "Gemeinsamkeiten" auszunutzen wie Du es richtig genannt hast.

Ich hätte es zum Beispiel auch "Schnittmenge" genannt, aber das kommt alles auf das selbe raus:

Ziel ist es über diese Gemeinsamkeiten einen neuen Typ zu schaffen (Interfaces sind Typen, genauso wie Klassen)

Die Liste in Deinem Beispiel müsste dann wohl so aussehen, oder ?

(Pseudocode, nicht getestet)

Ich bekomme hier die spitzen Klammern nicht angezeigt daher nutze ich [] für den Typ:

list[ITauchfaehig] tauchfaehigeFahrzeuge = new list
list[ITauchfaehig]

tauchfaehigeObjekte.add(einWasserfahrzeug)
tauchfaehigeObjekte.add(einTaucher)

Das wäre dann also eine Liste, die Objekte vom Typ ITauchfaehig enthält...?

Vielleicht noch ein anderes Beispiel bzgl. "Gemeinsamkeiten" wo es nicht um eine Liste geht sondern um eine gemeinsam nutzbare Methode geht:

Nehmen wir mal 3 Klassen an:
class cHolz
class cGas
class cKohle

Auf den ersten Blick haben diese Klassen anscheinend nicht so viel gemeinsam, man würde also nicht unbedingt zwischen diesen Klassen vererben müssen (also kein subclassing nutzen).

Jedoch kann man durchaus eine Gemeinsamkeit finden, nämlich all diese Stoffe sind brennbar, wenn auch mit unterschiedlichem Heizwert.

Ich möchte nun also diese Gemeinsamkeit in meinem Programcode auszunutzen um möglichst effektiven Code zu schreiben.

Daher fasse ich diese Klassen also über ihre Gemeinsamkeit (brennbar) zusammen indem ich einen neuen Typ (IBrennbar) mittels Interface schaffe (das ist dann wohl sog. subtyping) :

interface IBrennbar
{
int Heizwert;
}

class cHolz :IBrennbar
{int Heizwert = 10}

class cGas :IBrennbar
{int Heizwert = 20}

class cKohle :IBrennbar
{int Heizwert = 30}

Dann sollte das möglich sein :

IBrennbar oHolz = new cHolz()
IBrennbar oGas = new cGas()
IBrennbar oKohle = new cKohle()

//eine einzige Methode um alle brennbaren Stoffe zu verarbeiten

verheizen (oHolz)
verheizen (oGas)
verheizen (oKohle)

void verheizen(IBrennbar irgendwasBrennbares)
{
//hier jetzt irgendwas machen...z.B je nach Heizwert erhöht sich die Zimmertemperatur irgendwie
Zimmertemperatur += irgendwasBrennbares.Heizwert
}

Neben Deinem Beispiel mit der Liste und den tauchfähigen Objekten ist das vielleicht auch noch ein Beispiel für Mehrfachvererbung... hoffe ich jedenfalls

Aber vielleicht kann man mein oder auch Dein Beispiel anderes lösen als mit Interfaces...es gibt z.B auch dynamische Objekte wie "ExpandoObject" mit denen man so was vielleicht auch machen könnte...

danke nochmal und tschüss

1
KnorxyThieus  24.03.2016, 12:34
@FragerHH

Hallo,

Freut mich, wenn ich dir helfen konnte! :-)

Das mit der Schnittmenge ist auch eine exzellente Erklärung.

Zu deinem Code: Deine Überlegung ist richtig! Ich weiß nicht, ob das für dich von Belang ist, nur der Info halber, lauffähig sähe er so aus:

List<ITauchfaehig> tauchfaehigeFahrzeuge = new List<ITauchfaehig>();

tauchfaehigeFahrzeuge.Add(einWasserfahrzeug);
tauchfaehigeFahrzeuge.Add(einTaucher)

Die Syntax ist, wie du siehst, ein bisschen anders ... in deinem Buch kommst du wahrscheinlich auch noch zu generischen Typen, da wird dir das noch erklärt :)

Der Code bzgl. IBrennbar stimmt auch - nur würde man meistens die Methode Verheizen() direkt im Interface angeben, also:

interface IBrennbar
{
int Heizwert; void Verheizen(); }

Es sei denn, diese Methode ist wirklich vollkommen unabhängig von allen spezifischen Eigenschaften der implementierenden Klassen, zu Deutsch: Man wirklich nur die direkt im Interface angegebenen Informationen des Brennstoffes, hier den Heizwert, benötigt, um ihn zu entzünden. Das ist aber wirklich selten der Fall; in diesem Falle zum Beispiel läuft das Anzünden des Holzes möglicherweise anders ab als das des Gases: Beim Holz braucht man Anzünder ...

Zusatzinfo: Doch wenn die Methode überall genau die gleiche Definition, den gleichen Inhalt besitzt wie in deinem Beispiel, würde man sie i. d. R. als sgn. Erweiterungsmethode deklarieren: Dafür schreibt man sie in eine statische Klasse als statische Funktion wie folgt:

static class HeizTools
{
   void Verheizen(this IBrennbar irgendwasBrennbares)
{
Zimmertemperatur += irgendwasBrennbares.Heizwert; // ... }
}

Das hat den Vorteil, dass man beim Aufruf nicht

Verheizen(oGas);

schreiben muss, sondern bloß

oGas.Verheizen();

- also eine objektorientiertere Syntax verwenden kann. Die Programmierer diskutieren, ob das nicht nur syntaktischer Zucker - unnötige Schönheitspflege - ist, aber ich finde es ganz praktisch, weil der Befehl dann mit in der IntelliSense-Liste (sofern du Visual Studio nutzt) vorgeschlagen wird. Erweiterungsmethoden normalen statischen Methoden vorzuziehen, insofern sie passen, ist jedenfalls allgemeine Konvention.

Zu deinem letzten Absatz: Umsetzung mittels dynamischer Objekte? Bloß nicht! Diese bringen totale Typunsicherheit mit sich und sind sehr fehleranfällig! Man könnte sie aus Faulheit verwenden, doch es ist allgemein davon abzuraten. Hier kannst du dir dazu mehr durchlesen: http://stackoverflow.com/questions/2690623/what-is-the-dynamic-type-in-c-sharp-4-0-used-for
(Meine) allgemeine Empfehlung hierzu: Dynamische Objekte brauchst du nur, wenn du mit externen Programmen und Bibliotheken kommunizierst, also bis dahin ist es noch ein langer Weg.

So, ich hoffe, ich hab dich nicht zu sehr vollgespammt ... ;-)

MfG,
KnorxThieus

0
KnorxyThieus  30.03.2016, 11:51
@KnorxyThieus

Wenn ich dir helfen konnte, würde ich mich echt über die Auszeichung - Hilfreichste Antwort - freuen :-)

0

Amphibienfahrzeuge : ILandfahrzeuge, IWasserfahrzeuge

Amphibienfahrzeug muss dann alle Methoden, die in ILand- und IWasserfahrzeug deklariert sind, auch implementieren, hätte also (wenn man das so plant) zumindest alle öffentlichen Methoden der beiden anderen Fahrzeuge.