Interfaces

Status
Nicht offen für weitere Antworten.

hdi

Top Contributor
Vom Sinn und Zweck von Interfaces

Ziel dieses Beitrags ist es, Anfängern das Prinzip von Interfaces näher zu bringen.

Vermutlich hast du schon davon gehört, und wahrscheinlich kennst du auch schon die genaue Syntax.
Das ist nicht die Schwierigkeit, man muss auch verstehen wieso Interfaces "so toll" sind.
Let's go...

"Interface" ist natürlich Englisch und bedeutet wörtlich übersetzt Zwischengesicht. Hier ist aber was anderes gemeint ;) Die eigentliche Übersetzung ist "Schnittstelle", aber auch das find ich nicht so toll, weil dieses Wort ziemlich unintuitiv ist, und man sich nicht wirklich etwas darunter vorstellen kann.

Für dieses Tutorial übersetze ich das Wort Interface mal mit dem Wort Schablone. Das nehmen wir jetzt einfach mal so hin, im Laufe dieses Textes wird man hoffe ich verstehen können, wieso ich dieses Wort wähle.

Interfaces kommen zum Beispiel zum Einsatz, wenn man Eigenschaften verschiedener Objekte in einer Klasse zusammenführen möchte (z.B. Mehrfachvererbung) oder man Klassen, die thematisch nichts miteinander zu tun haben, Gemeinsamkeiten geben will.

Schauen wir uns an was ein Interface in Java ist: (Bsp)

Java:
public interface Buyable{}

Wie auch bei Klassen braucht ein Interface keinen echten Inhalt. Natürlich ist das obige Beispiel absolut sinnlos, aber syntaktisch korrekt und kompilierbar.

Normalerweise sieht man in Interfaces aber sowas hier:

Java:
public interface Buyable{

      public int getPrice();

      // evtl. weitere derartige Methoden-Definitionen
}

C:
getPrice()
scheint eine Methode zu sein, sie hat einen Rückgabewert, einen Namen, und die Klammern sind ein weiteres Hinweis darauf, dass es sich um eine Methode handelt.

Aber was sofort auffällt: Der ";" hinter den Klammern. Normalerweise wäre das keine gültige Methodendefinition, denn eine Methode braucht bekanntermaßen einen Rumpf, also "{...}". Das fehlt hier.

Es ist genauer nicht nur so, dass das bei diesem Bsp fehlt, sondern so, dass Methoden in Interfaces so aussehen müssen. Sie dürfen keinen Rumpf haben.

Okay, bevor wir das näher unter die Lupe nehmen, hier die Java-Syntax davon, wie man ein Interface nutzt (man sagt auch "implementiert"):

Java:
public class Car implements Buyable{

    @Override
    public int getPrice(){
           return 15000;
    }
}

Zwei Dinge sind hier wichtig: Erstmal das "implements Buyable". Das alleine würde aber eine ungültige Klasse erstellen, denn es gibt eine Regel:

Eine Klasse, die ein Interface implementiert, muss alleMethoden implementieren,
die dieses Interface definiert.


"Implementieren" heisst nun konkret: Wir schreiben die Methode in die Klasse, so, wie sie auch im Interface steht, und fügen aber nun einen Rumpf hinzu, in den wir die konkrete Implementierung schreiben. Also das, was die Methode machen soll. Wie bei normalen Methoden.
Genau genommen ist das jetzt in der Car-Klasse eine "normale" Methode, sie muss halt nur da sein, wegen dem Interface.

Eine kleine Anmerkung zum Override: Das ist eine Java-Annotation, die andeutet, dass diese Methode von einer Klasse geerbt wurde/bzw. von einem Interface übernommen, und nun in dieser Klasse überschrieben bzw. implementiert wird. Man kann das weglassen, es ist quasi nur "guter Stil" das dazu zu schreiben, damit man sofort sieht was Sache ist...
Außerdem wird der Compiler Fehlermeldungen ausspucken, wenn er bemerkt, dass wir eine Methode überschreiben wollten, aber sich bspw. durch einen Tippfehler ein Fehler in den Methodennamen oder die Methodensignatur eingeschlichen hat. Das kann uns später eine Menge Ärger und eine mühsame Fehlersuche ersparen.

Soweit so gut. Ein Auto bietet nun die Methode
Code:
getPrice()
, und wenn ich sie auf einem Car-Objekt aufrufe, kriege ich 15000 zurück.

Du denkst dir jetzt wohl: "Ich könnte die getPrice() Methode auch einfach so in eine Klasse schreiben, ohne davor ein Interface zu erstellen, in der ich sie definiere, um es danach in der Car-Klasse zu implementieren. Das ist doch einfach nur umständlich mit nem Interface?!".

Ich sage: Da haste Recht :toll:

Aber wie du am Scrollbalken rechts sehen kannst, war's das noch nich so ganz ;)

Um zu verstehen, was daran jetzt toll sein soll, sehen wir uns zuerst einmal das folgende Code-Bsp an.
Gegeben seien folgende simple Klassen:

Java:
class Car {

    public int getPrice() {
        return 15000;
    }
}

class Truck {

    public int getPrice() {
        return 45000;
    }
}

class Motorbike {

    public int getPrice() {
        return 8000;
    }
}

Desweiteren haben wir nun einen Händler, der Autos, LKWs und Motorräder verkauft.
Die Klasse, die diese Dienstleistung repräsentiert, könnte zB so aussehen:

Java:
class CarDealer {

    private int billingAmount;

    public void buyCar(Car c) {
        billingAmount += c.getPrice();
    }

    public void buyTruck(Truck t) {
        billingAmount += t.getPrice();
    }

    public void buyMotorbike(Motorbike m) {
        billingAmount += m.getPrice();
    }

    public void printBill() {
        System.out.println("Der Betrag Ihres Einkaufs beläuft sich auf:");
        System.out.println(billingAmount + " €.");
        System.out.println("Danke für Ihren Einkauf bei MegaMüll Vehicles.");
    }
}

Ich denke, es ist dir klar dass ich jetzt einen Einkauf wie folgt tätigen könnte:

Java:
public class Buy {

    public static void main(String[] args) {
        CarDealer carDealer = new CarDealer();
        carDealer.buyAuto(new Car());
        carDealer.buyTruck(new Truck());
        carDealer.buyMotorbike(new Motorbike());
        carDealer.printBill();
    }
}

So... bevor ich jetzt letztendlich wieder auf Interfaces zurückkomme, möchte ich, dass du über folgendes nachdenkst:
Angenommen, der Händler möchte expandieren und noch mehr anbieten, als bisher. Z.B. möchte er ab sofort auch Fahrräder verkaufen, Kühlschränke, ToastBrot und Messersets. (Warum auch immer)

Was müsstest du tun, wenn du das jetzt Code technisch realisieren willst?
Du müsstest zuerstmal diese neuen Klassen erstellen, und jeder Klasse würdest du eine
Code:
getPrice()
Methode geben, die einen passenden Betrag zurückgibt.
Aber das war's ja noch nicht.
Du müsstest weiterhin in der Händler-Klasse eine neue buy-Methode für jeden Typ von neuer Klasse erstellen. Ist ja eigentlich nicht schwer. Wenn ich dir nun aber sage, dass der Händler auch noch folgende Dinge verkaufen möchte:

Schmuck, Mp3-Player, DVDs, Computer, Fernseher, Bier, Äpfel, Bananen, Hund, Katze, Maus, Kindergarten...

UND: Man soll einen Artikel auch von der Rechnung streichen können, falls man sich doch anders entscheidet. Das geht bisher ja nicht. Wenn du 12 Autos gekauft hast, und nun das letzte wieder revidieren willst, geht das nicht. Dann muss der Händler von vorne beginnen (neue Instanz von Händler) und du musst nochmal 11 Autos kaufen (11 Aufrufe von
Code:
buyCar(new Car());
)

Das könntest du analog zu den buy-Methoden mit zB cancel-Methoden realisieren, die das gleiche machen wie die buy-Methoden, nur den Betrag, den getPrice() liefert, eben abziehen von der Rechnung.

Da kommt jetzt also 'n bisschen Arbeit auf dich zu, und du kannst nur hoffen, dass der Händler nicht plötzlich noch weitere 124 Artikel verkaufen möchte, weil das weitere 124 buy()- sowie 124 cancel()-Methoden = 248 Methoden in der Händler-Klasse wären.
Geil... :eek:

Das ist jetzt unser Ausgangs-Szenario für die Erklärung von Interfaces.

Das gleiche Beispiel von oben mit der Nutzung von einem Interface:

Java:
interface Buyable() {
    public int getPrice();
}

class Car implements Buyable{

    @Override
    public int getPrice() {
        return 15000;
    }
}

class Truck implements Buyable{

    @Override
    public int getPrice() {
        return 45000;
    }
}

class Motorbike implements Buyable{

    @Override
    public int getPrice() {
        return 8000;
    }
}

class CarDealer {

    private int billingAmount;

    public void buyBuyable(Buyable b) {
        billingAmount += b.getPrice();
    }

    public void printBill() {
        System.out.println("Der Betrag Ihres Einkaufs beläuft sich auf:");
        System.out.println(billingAmount + " €.");
        System.out.println("Danke für Ihren Einkauf bei MegaMüll Vehicles.");
    }
}

...und.. was fällt auf? Wo sind die ganzen buy-Methoden? Es gibt nur noch eine!

Und das hier:

Java:
public class Buy {

    public static void main(String[] args) {
        CarDealer carDealer = new CarDealer();
        carDealer.buyBuyable(new Car());
        carDealer.buyBuyable(new Truck());
        carDealer.buyBuyable(new Motorbike());
        carDealer.printBill();
    }
}

ist noch immer das selbe, und es funktioniert.

Und ich nehm's gleich mal vorweg: Weisst du, was du in der Händler-Klasse ändern musst, wenn der Händler nun auch noch zwei Millionen siebenhunterachtundvierzigtausendneunhundertfünf weitere verschiedene Artikel verkaufen möchte?
N I C H T S.

Und was ist mit dem storno-Zeugs? Anstatt jetzt hunderte Methoden Á la:

Java:
cancelCar(Car c);
cancelMotorbike(Motorbike m);
// usw.

zu schreiben, reicht nur eine einzige Methode, analog zu der
C:
buyBuyable()
:

Java:
cancelBuyable(Buyable b){
     billingAmount -= b.getPrice();
}

Fertig!

So, ich denke jetzt kannst du wohl nachvollziehen, wieso Interfaces so toll sind, oder?
Sie sparen dir viel Zeit, viel Ärger, und das Modifizieren von Funktionalität ist leichter.

Aber wieso geht das überhaupt?
Die Antwort darauf schimpft sich "Polymorphie", und ist eines der wichtigsten und mächtigsten Features von objektorientieren Programmiersprachen, wie eben Java zB auch eine ist.

Konkret besagt Polymorphie in Java (unter anderem):

Wenn eine Klasse A von einer anderen Klasse B erbt, oder ein Interface B implementiert,
dann ist jedes Objekt der Klasse A auch gleichzeitig ein Objekt vom Typ B.


Bei Interfaces ist das nicht ganz so verständlich wie bei Vererbung, aber es ist nun mal so.
Wenn man ein Interface "Buyable" hat, dann ist Buyable ein gültiger Datentyp, denn man überall verwenden darf. Und er steht für ein Objekt, dessen Klasse das Interface implementiert. Total egal, was das nun genau für eine Klasse ist, oder was sie neben den im Interface definierten Methoden noch so anbietet.

Deshalb hab ich anfangs auch von "Schablonen" geredet: Wenn man an einer Stelle im Programm ein Objekt des Typs vom Interface hat (also ein Buyable), dann wird quasi über den Code von der Klasse, von der das Objekt eigentlich ist (Auto, Motorrad usw) eine Art Schablone gelegt, die alle Methoden, Variablen und sonstige Dinge ausblendet, und nur genau dort ein "Loch" hat, wo die Methoden stehen, die aus dem Interface übernommen sind.

So ist dieses Objekt an dieser Stelle im Programm einfach ein Buyable, und man kann nur die Methoden aus diesem Interface aufrufen. Was das genau ist, spielt keine Rolle, es interessiert den Händler einfach nicht. Er schreibt 'ne Rechnung, und solange er einen Geldbetrag hat, schreibt er den auf. Ist ihm doch Wurscht, was für ein Artikel dahinter steht, er will nur die Kohle sehen.
Wenn du nun also eine Klasse hättest:

Java:
public class DealersMother implements Buyable{
        public int getPrice(){
                return 1;
        }
}

dann würde der Kerl dir eiskalt seine eigene Muter für 'n Euro verchecken.

Er blendet alles andere bis auf das, was ihm wichtig ist, aus. Schablone drüber, fertig. Und wichtig ist ihm nur, dass er eine getPrice() Methode aurufen kann... Der kriegt gar nicht mit, dass du grade seine Mutter kaufst.

So, ich habe fertig.

Merksatz:
Ein Interface zu implementieren macht nur dann Sinn, wenn an einer
Stelle im Programm dieses Objekt auch nur als Typ des Interfaces angesehen wird.
Also Buyable als Datentyp.
 
Zuletzt bearbeitet von einem Moderator:
Status
Nicht offen für weitere Antworten.

Neue Themen


Oben