Sinn von Interfaces

Hag2bard

Bekanntes Mitglied
Hallo und guten Abend,

Ich habe so weit ich das beurteilen kann ein paar Grundkenntnisse in Java und bin dabei mir das Buch Entwurfsmuster von Kopf bis Fuß durchzulesen. Momentan bin ich noch ganz am Anfang auf Seite 46.
Hier geht es darum dass eine Superklasse Ente und eine Unterklasse z.b. Stockente oder Gummiente entweder ein Verhalten hat oder eben nicht.
Eine Gummiente kann nicht quaken, soweit hab ich das verstanden.
Also sollen die Unterklassen ein Interface implementieren, welches das Verhalten beinhaltet.
Im Fall dass es eine echte Ente ist, überschreibe ich die Methode quaken, wenn es eine Gummiente ist, dann hat die Methode quaken keine Funktion.
Aber was ich mich nach all den Tutorials frage: Warum soll das weniger aufwendig sein ?
Ich muss in jeder Unterklasse die quaken kann sowieso einzeln die leere Methode implementieren und überschreiben. Da kann ich mir die Interface Geschichte doch gleich sparen und die Methode einzeln in jeder Klasse erstellen. Ist doch genau der selbe Aufwand. Außer dass ich in den Unterklassen wie z.b. Gummiente keine Methode implementieren muss sondern einfach keine schreibe.

Also für was macht man das dann so?
 
Interfaces sind wie eine Vorschrift bzw. Schnittstelle. Alle Klassen, welche ein Interface implementieren, müssen dessen Vorschriften komplett umsetzen. Ein Interface ist also keine Superklasse sondern beschreibt die Mindestanforderungen an eine Klasse. Es kann also von der Superklasse Ente geerbt werden und somit erfüllt die abgeleitete Klasse immer die Anforderungen von Ente. Gleichzeitig kann auch noch ein Interface Implementiert werden. Dieses muss nichts mit Ente zu tun haben, z.B.: Farbe (Methode: getFarbe). Auch ein Auto oder eine Pflanze kann Farbe implementieren ohne eine Ente zu sein. Somit wäre es möglich Objekte weiter zu klassifizieren. Wäre die Methode getFarbe nur in Ente vorhanden, müsste eine Pflanze immer auch eine Ente sein. Bei einem Auto wäre das ja noch möglich, aber mit dem "quaken" wird es hapern 😉
 
Und wie verhält man sich wenn man z.b 200 Unterklassen hat, von denen 150 quaken können und ich möchte das Quaken als solches verändern?
Dann muss ich die 150 Klassen ändern?
Warum sollte ich nicht einer Superklasse Ente haben , eine mittlere , die von der Superklasse erbt und eine nicht leere Methode quaken hat, von der dann die Unterklassen bei Bedarf erben oder wenn die Unterklassen nicht quaken dann erben sie direkt von der Superklasse
 
Also für was macht man das dann so?
Ein Interface spezifiziert, wie man ein Objekt, das eben dieses Interface implementiert, verwenden kann. Die Implementierung kann dagegen ganz unterschiedlich ausfallen.

Einfaches Beispiel: stell Dir vor, Du hast ein Interface Switchable, das zwei Methoden besitzt: switchOn und switchOff. Nehmen wir an, Du hast nun ein GUI mit einem Button. Dieser Button kann nun die Methoden eines Objekts mit Switchable-Schnittstelle bedienen, egal, was dieses Objekt macht. Das Switchable-Objekt kann der Toaster, der Fernseher oder auch ein AKW sein - spielt für den Button keine Rolle. Das Switchable-Objekt ist also austauschbar, denn der Button muss nur wissen: switchOn zum Ein- und switchOff zum Ausschalten, fertig.
 
Das ist wirklich schwer zu verstehen. Habt ihr noch mehr Beispiele? Wieso erstelle ich nicht einfach die 2 Methoden in den jeweiligen Klassen?
 
Das ist wirklich schwer zu verstehen. Habt ihr noch mehr Beispiele? Wieso erstelle ich nicht einfach die 2 Methoden in den jeweiligen Klassen?
Weil du dann Probleme bekommst, wenn du Objekte an eine Methode übergeben willst.

Beispiel:

Java:
class Ente {
}

class EnteA extends Ente {
public void quaken() {}
}

class EnteB extends Ente {
public void quaken() {}
}

class EnteC extends Ende {
// Kein quaken
}


public void randomMethode(Ente ente) {
ente.quaken(); // Kompiliert nicht
}
Das heißt jeder, der deine Enten Klasse verwedet muss alle Ableitungen kennen und wissen, welche davon quaken implementieren - das ist nicht sinnvoll.
 
Sieh Interfaces (oder auch Methoden in Oberklassen) als Contract / Vertrag.

Derjenige, der die verwendet, muss nicht wissen:
* Welche Ableitungen es gibt
* Wie die implementiert sind


Sondern er weiß, es gibt die Methode X die laut Dokumentation Y machen soll. Ein Beispiel ist z.B. das List Interface - das hat eine Methode size() die laut Spezifikation die Größe der Liste zurückgeben muss. Ich weiß, wenn ich ein List-Objekt in der Hand habe, kann ich size() aufrufen und bekomme die Größe zurück. Das ist der Contract der durch das Interface definiert ist.

Genauso wie es beim Auto den Contract gibt, das ein Auto ein Gaspedal und ein Bremspedal hat. Das heißt, ich kann mich in jedes Auto reinsetzen und weiß - rechts Gas, links davon Bremse. Würde es das nicht geben, müsstest du bei jedem Auto erst in die Anleitung schauen, wie man beschleunigt und bremst und jeder der ein neues Auto (=neue Ente Ableitung) baut, hat nirgendwo vorgeben, wie sich das Auto verhalten muss.
 
Werden Interfaces in der Praxis auch benutzt oder ist das eine Theorie? Meine momentane Vorstellungskraft lässt nicht zu, dass Interfaces einen Zweck erfüllen.
 
Das ist wirklich schwer zu verstehen. Habt ihr noch mehr Beispiele? Wieso erstelle ich nicht einfach die 2 Methoden in den jeweiligen Klassen?
Nehmen wir als Beispiel das Interface:
Java:
public interface GeometricShape {
    public double area();
}
Es ermöglicht die Fläche, einer geometrischen 2D Figur zu berechnen.
Wir erstellen folgende Klassen, die dieses Interface implementieren.
Java:
public class Circle implements GeometricShape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }
    @Override
    public double area() {
        return radius * radius * Math.PI;
    }
}
Java:
public class Rectangle implements GeometricShape {
    private double a;
    private double b;

    public Rectangle(double a, double b) {
        this.a = a;
        this.b = b;
    }
    @Override
    public double area() {
        return a * b;
    }
}
Eine weitere Klasse Square erbt von Rectangle
Java:
public class Square extends Rectangle {

    public Square(double a) {
        super(a, a);
    }
}
Wir benutzen nun ein Array, das Objekte vom Type GeometricShape beinhaltet, um die Flächen dieser Objekte auszugeben.
[CODE lang="java" title="TestGeometricShape" highlight="4-7"]public class TestGeometricShape {

public static void main(String[] args) {
GeometricShape[] shapes = { new Circle(20), new Rectangle(10, 5), new Square(3) };
for (GeometricShape s : shapes) {
System.out.println("Flaeche:\t" + s.area());
}
}
}[/CODE]
Würde man die Klassen Circle, Rectangle, Square ohne Interface oder Vererbung planen, wäre die Ausgabe in der Form nicht möglich.
Der Schleife ist es an der Stelle egal ob es sich um einen Kreis, ein Quadrat oder ein Rechteck handelt. Sie kennt das Interface und weiß nur, dass es die Flächenfunktion area() gibt und sie benutzt diese sehr erfolgreich. 😉
 
Werden Interfaces in der Praxis auch benutzt oder ist das eine Theorie? Meine momentane Vorstellungskraft lässt nicht zu, dass Interfaces einen Zweck erfüllen.
Sie werden in der Praxis dauernd benutzt.

Code, den ich mindestens 1x am Tag schreibe:

Java:
List<Type> myList = new ArrayList<>();
// oder
List<Type> myList =someMethod();
List ist ein Interface, ohne Interface ginge sowas nicht.

Sämtliche Lambda Ausdrücke funktionieren nur dank Interfaces (gut, das ist ein Sonderfall) wie z.B.

Java:
myList.stream().filter(this::isRelevant)
Der Teil mit isRelevant geht nur weil es Interface Predicate gibt.

Wir haben Projekt mindestens eine höhere 2-stellige Anzahl an eigenen Interfaces, dreistellig glaube ich aber eher nicht (bei ca. 2700 Klassen insgesamt)

Interfaces sind eines der wichtigsten Mittel in Java, sie sind an vielen Stellen deutlich sinnvoller als Vererbung mit konkreten oder abstrakten Klassen.

Edit Ich muss mich korrigieren, auf ca. 2700 Klassen (inkl. Unit-Tests) kommen ca. 120 definierte Interfaces (grep sei Dank 🙂)
 
Das Buch, das du gerade liest, zeigt an Hand von Beispielen auch die ganzen Dinge auf.

Somit ist es gut möglich, dass du das beim weiteren Durchgehen des Buches besser verstehen kannst.

Interfaces sind aber extrem wichtig. Dies kann man evtl. an Hand von gewissen Dingen begreifen:
Zum einen ist es so, dass jede Klasse in Java nur von einer anderen Klasse abgeleitet werden kann. Aber evtl. soll es noch andere Verhaltensweisen haben. Reine Vererbung kann dies also nicht abbilden.
Ein Blick in die reale Welt zeigt es doch auch: Du hast Dinge, die zu einer Ordnung gehören. Ein 2 Cent Stück ist eine Münze, ist Geld - wenn man eine Art Vererbungshierarchie aufstellen soll. Aber dann kommen wir zum Berhalten:
Es kann eingeschmolzen werden. Man kann es ggf zum drehen einer Schraube verwenden. Ein Wurfgeschoss ist es. ....

Oder Bedienung: es gibt irgend welche bekannten Bedienungen: diese werden dann immer weiter genutzt und das macht es dann einfach bei der Bedienung oder Nutzung. Wenn ich ein Interface definiert habe, dann kann ich da alles austauschen. Die Festplatte im Computer kann ich tauschen - so das Interface gleich bleibt. Da kann sogar die Technik sich verändern, d.h. die Festplatte kann ganz anders sein, kann sogar keine Festplatte sein (CD / DVD Laufwerk oder sonst irgendwas) ...
Ober bei der Bedienung: Du kannst Auto fahren? Aber doch nicht alle Autos sondern nur die, die das passende Interface haben. (Wenn ich ein Auto baue, das ganz anders bedient wird, dann kannst du das erst einmal nicht fahren!). Und das ist ein Interface, d.h. du kannst es auch da nutzen, wo du gar kein Auto hast. (Und das Interface nutzt ja auch nur andere Interfaces: Schalter, Hebel, ...)
 
Es ist auch so, dass sich Interface und eine (meist abstrakte) Superklasse nicht widersprechen, denn auch die Superklasse könnte ja ein Interface implementieren.

Schau dir doch mal das Interface List<> an: https://docs.oracle.com/javase/7/docs/api/java/util/List.html

Ziemlich weit oben, steht, welche Klassen dieses Interface implementieren: AbstractList, AbstractSequentialList, ArrayList, AttributeList, CopyOnWriteArrayList, LinkedList, RoleList, RoleUnresolvedList, Stack, Vector

Springst du dann zur ArrayList<>: https://docs.oracle.com/javase/7/docs/api/java/util/ArrayList.html, dann siehst du die Klassenhierarchie auch ganz oben und du siehst auch folgendes:
public class ArrayList<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable

Jede Methode in deinem Programm, die als Parameter eine List<> erwartet, der kannst du jede der oben genannten Implementationen übergeben. Dazu kommt noch, dass du auch eigene Klassen schreiben kannst, die List<> implementieren.

Aber falls es dich tröstet: Als ich den Interfaces vor vielen Jahren mit C# zum ersten Mal begegnet bin, habe ich mich genauso gefragt, was das soll. Die Entwurfsmuster haben mir da viele Erkenntnisse beschert. Ich würde also einfach mal weiterlesen. Vielleicht klärt sich damit noch einiges.
 
Interfaces sind unter anderem die Lösung in Java Mehrfachvererbung zu machen.
Man wollte nicht das sogenannte Diamanten-Problem von C++ haben(vieles in Java sah sich damals als Verbesserung zu C++).
In Scala gibt es zum Beispiel Traits, wahrscheinlich eine bessere Lösung.
 
Man sollte auch an den Unterschied API und SPI denken.

API: Application Programming Interface (aktiver Code ruft Unterprogramm auf: Lib)
SPI: Service Provider Interface (aktiver Code ruft Nutzcode auf: Framework)
(hier steht Interface nicht für Java Interface sondern allgemein)

Grafische Oberflächen wie Windows (in Java Swing oder JavaFx oder SWT) benötigen eine Verbindung zum Nutzcode, den die grafische Oberfäche (Windows) aufruft.

In C/C++ gab es dafür sogenannte Hooks mit Funktionszeigern.

In Java gibt es dafür Klassen, die ein bestimmtes Interface implementieren.
Auf Objekte wird in Java sowieso nur per Referenz verwiesen (analog zu Zeigern).
 
Meine Lieblings-Java-Interfaces sind

java.util.Iterator
java.util.Iterable

Gegenüber dem C++ Iterator, der unbedingt Speicheradressen verlangt, ist java-Iterator viel sauberer definiert.

[CODE lang="java" title="Iterator"]// gibt es ein nächtes Element
boolean hasNext();

// gib das nächte Element zurück und schalte weiter
T next();
[/CODE]

Dadurch könnten die Elemente auch aus einer Datei oder aus dem Netzwerk stammen.

Übrigens benötigt die Java-for-each-Schleife Iterable.
 
Vielleicht ein ganz kleines praktisches Beispiel dazu. Angenommen du möchtest eine Methode implementieren, die mehrere als Parameter übergebene Strings auf der Konsole ausgibt.

Im einfachsten Fall etwas wie printAll(String str1, String str2, String str3). Das ist nicht sehr flexibel, denn du kannst nur drei (und muss auch immer genau drei) Strings angeben, die anschließend ausgegeben werden.

Besser wäre es etwas Flexibleres zu haben, wie z. B. eine ArrayList<String>, also printAll(ArrayList<String> str). Das ist bereits viel flexibler, aber es wird immer noch nur der Datentyp ArrayList von der Methode als Parameter akzeptiert.

Nehmen wir also die bereits erwähnte List<String> als Parametertyp printAll(List<String> str). Das ist schon sehr gut, denn jetzt kannst du alle Implementationen von List<> als Parameter übergeben.

Es geht noch besser. In der Methode musst du ja nur Zugriff auf die einzelnen Elemente der Liste haben, damit du sie nacheinander ausgeben kannst. Dafür bietet sich Iterable<String> an. Dieses Interface wird von sehr vielen Klassen implementiert. Und alle diese Klassen kannst du als Parameter an die Methode übergeben.
 
Vielleicht nutzen wir das Interface List einfach einmal in einem (etwas an den Haaren herbei gezogenen) Beispiel:

Du schreibst ein Programm (Verwaltung Deiner Musik, Filme, Bücher, ...) und du speicherst eine ganze Menge Daten. Also schaust Du umher und stolperst über LinkedList. Die Klasse klingt super, einfach verkettete Listen kennst Du auch vom Unterricht / Vorlesung / woher auch immer ... top!

Nun fängst Du an, da massiv Daten zu speichern und du stellst fest: Scheiße ist das langsam! Und dann findest Du: ArrayList greift in O(1) auf Elemente zu - das ist ja top!
==> Also gehst Du durch den ganzen Code um das zu verändern. Dauert etwas, wenn Du musst ja wirklich alle betroffenen LinkedList finden. Du hast auch noch andere LinkedLists, die willst Du aber nicht ändern ... Scheiß Arbeit, aber irgendwann hast Du es geschafft ....

Nun findet Du eine super Quelle mit Daten (Also Film-Datenbanken oder so und findest das geil. Von da willst Du jetzt auch massiv Daten importieren. Aber scheisse: Jetzt fliegt es Dir um die Ohren, weil die ArrayList ja alles im Speicher hält.

Datenbank nutzen ja, aber dir gefällt die Applikation so eigentlich ganz gut - da willst Du nichts ändern, daher hast Du einfach eine Klasse "DatabaseBasedList" geschrieben (Weil was anderes willst Du nicht - da mit den IDs und so ist genial (Sei es mal so)!) Also gehst Du dann durch und ersetzt nun ArrayList durch DatabaseBasedList.

Hättest Du generell einfach nur mit "List" gearbeitet, dann wäre die Änderung nur an ganz wenigen Stellen notwendig gewesen, da nur die Stellen, die die Instanzen erzeugen, angepasst werden müssen.

Einfach nur um ein kleines Szenario aufzuzeigen. Diese Szenarien so gibt es ständig. Das wird sich auch etwas durch das Buch ziehen. Das OO-Prinzip "Programmieren sie auf eine Schnittstelle und nicht auf eine Implementierung" hast Du ja schon bekommen (Seite 32 so wir die gleiche Auflage haben). Ebenso den Hinweis, dass man sich das Prinzip im weiteren Verlauf des Buches noch genauer ansehen wird. Kapitel 4 wird das z.B. noch genauer zeigen, wenn es um das Factory Pattern gehen wird. "new" geht halt nur mit einer konkreten Implementierung und daher wird da dann gezeigt, wie man das noch Kapseln kann und so ...
 
Das ist wirklich schwer zu verstehen. Habt ihr noch mehr Beispiele? Wieso erstelle ich nicht einfach die 2 Methoden in den jeweiligen Klassen?
Grundsätzlich spricht da nichts dagegen. Interessant wird's, wenn du von außen darauf zugreifen willst ohne wissen zu wollen, um welche Klassen es sich handelt.

Für meine Datenstrukturen hab ich z.B. eine KeySupplier Interface für alle Datenobjekte, die einen in der jeweiligen Kategorie eindeutigen String-Key haben.
Dafür gibt's dann verschiedene Methoden, die etwas mit einer Liste von KeySuppliern machen, die z.B. prüfen, ob alle Key eindeutig sind usw. Ist jetzt nichts Besonderes, aber der Punkt ist, dass mir dabei egal ist, um welche Objekte es sich handelt. Für den generischen Aufruf hab ich definiert, dass alle Objekte in der Liste die selbe Klasse haben oder erweitern müssen (undefiniert, Hauptsache immer die selbe) und dass sie KeySupplier implementieren müssen.
So muss ich diese Methoden nicht für jede Klasse einzeln schreiben.
 

Zurück
Oben