Keylearnings:
- Warum du Java Interfaces verwenden solltest.
- Was ist Interface Polymorphie?
- Was ist setter- Konstruktor Dependency Injection?
Du musst nicht wissen wie der Strom in die Steckdose kommt.
Auch nicht wie er da wieder herauskommt. Außer du bist Elektriker.
Es reicht völlig, dass du weißt wie du den Stecker deines Computers, Fernsehers oder Föhns in die Dose stöpselst.
Alles worauf es ankommt ist, dass der Stecker in die Dose passt!
Genau dieses Konzept können wir mit Hilfe von Interfaces auch in unseren Java Programmen verwenden.
In der Programmierwelt sprechen wir hierbei vom Prinzip des Least Knowledge oder auf deutsch das Prinzip vom wenigen Wissen.
In diesem Artikel möchte ich dir die Verwendung von Java Interfaces anhand des Beispiels der Zahlungsmöglichkeiten in einem Webshop erklären und die Frage warum du Interfaces verwenden solltest beantworten.
Hierbei werden wir auch über Konzepte wie Interface Polymorphie und Dependency Injection sprechen.
Setze auf Interfaces!
Ich predige es auf meinem Blog immer und immer wieder.
Verwende Interfaces!
Doch warum eigentlich?
Auf den ersten Blick erscheint dieses Konzept höchst langweilig zu sein. Schließlich können wir in einem Java Interface lediglich statische Variablen und Methodensignaturen definieren.
Das coole hierbei ist jedoch, dass wir mit Hilfe von Interfaces die Definition und die eigentliche Implementierung einer Funktionalität voneinander trennen können.
Wodurch wir die Möglichkeit haben eine Funktionalität zu verwenden ohne uns mit Implementierungs-Details beschäftigen zu müssen.
Die einzige Information, die wir benötigen ist, welchen Input braucht eine Funktionalität und welchen Output erhalten wir zurück.
Und genau das ist es worum es beim Prinzip des Least Knowledge geht.
Java Interfaces bieten uns also die Möglichkeit die beiden Fragen „Was wir tun wollen?“ und „Wie wir es tun?“ voneinander zu trennen.
Herrje, und was bringt uns das nun schon wieder?
Schauen wir uns das am praktischen Beispiel eines Webshops an.
Damit der Webshop Umsatz generiert, muss der Anwender die Möglichkeit haben Rechnungen zu bezahlen.
Hierfür stehen unterschiedliche Zahlungsmethoden zur Auswahl. Der Kunde kann mit Kreditkarte, per EC-Karte oder mittels des Zahlungsanbieters PayPal bezahlen.
Wie sieht die Implementierung des Webshops ohne die Verwendung von Interfaces aus?
Wir erstellen eine Klasse mit dem Namen Webshop, die für jede Zahlungsart eine Methode zur Verfügung stellt.
1: public class Webshop { 2: private String name = null; 3: public Webshop(String name) { 4: this.name = name; 5: } 6: public void zahlePerEC(){ 7: System.out.println("Zahlungslogik EC"); 8: } 9: public void zahlePerPayPal(){ 10: System.out.println("Zahlungslogik PayPal"); 11: } 12: public void zahlePerKreditkarte(){ 13: System.out.println("Zahlungslogik Kreditkarte"); 14: } }
Neben einem Attribut name
, das mit Hilfe des Konstruktor initialisiert werden und den Namen des Webshops enthält, besteht die Klasse aus den drei Methoden zahlePerEC
,zahlePerPayPal
und zahlePerKreditkarte
, welche Dummy-Implementierungen der verschiedenen Zahlungsarten enthalten.
Doch welchen Nachteil hat diese Vorgehensweise?
Was machen wir, wenn beispielsweise PayPal beschließt ihre Implementierungslogik zu ändern?
Japp, dann haben wir richtig Stress!
Wir müssten dann nämlich die Implementierung der zahlePerPayPal
Methode innerhalb unseres Webshops anpassen.
Und um genau das zu vermeiden können wir Java Interfaces verwenden.
Interfaces ermöglichen es uns die Implementierung der Zahlungsarten aus der Klasse Webshop zu entfernen und uns hier lediglich auf die eigentliche Aktion, nämlich die Zahlung, zu konzentrieren.
Wie die Zahlung letztlich erfolgt (PayPal, Kreditkarte etc.) spielt dann für die Webshop Klasse keine Rolle mehr.
Okay, gehen wir es an. Wir definieren ein Java Interface IZahlung
.
public interface IZahlung { public void erzeugeZahlung(); }
Das Interface enthält nur eine Methodensignatur erzeugeZahlung
.
Um diese Methode zu implementieren, d.h. mit Funktion zu füllen, müssen wir es in eine Klasse einbinden.
Da wir in unserem Webshop insgesamt drei verschiedene Zahlungsarten anbieten, erstellen wir für jede Art eine eigene Klasse, die das Interface IZahlung
implementiert.
Alle drei Klassen sind Strukturgleich.
Beginnen wir mit der Klasse, welche die PayPal Funktionalität implementiert.
1: public class PayPal implements IZahlung { @Override 2: public void erzeugeZahlung() { 3: System.out.println("Zahlungslogik PayPal"); 4: } 5:}
Mit Hilfe des Schlüsselwortes implements
binden wir in Zeile eins zunächst das Java Interface IZahlung
in die Klasse ein.
Hierdurch werden wir vom Compiler gezwungen die im Interface IZahlung
definierte Methode erzeugeZahlung
, welche die PayPal Zahlungslogik beinhaltet zu überschreiben.
Nahezu identisch sehen die Klassen für die Kredit- und EC-Kartenzahlung aus.
Hier die Klasse für die EC-Kartenzahlung:
public class EC implements IZahlung{ @Override public void erzeugeZahlung() { System.out.println("Zahlungslogik EC"); } }
Und fast ohne Unterschied die Klasse für die Kredikartenzahlung:
public class Kreditkarte implements IZahlung{ @Override public void erzeugeZahlung() { System.out.println("Zahlungslogik Kreditkarte"); } }
Unsere drei Klassen unterscheiden sich also lediglich in der Art wie die Methode erzeugeZahlung
definiert ist.
Das nächste Ziel ist es, die erzeugeZahlung
Methode in unserem Webshop zu verwenden.
Interfaces und Polymorphie
Um die verschiedenen Zahlungsarten in unserem Webshop verwenden zu können, benutzen wir das Konzept mit dem coolsten Namen überhaupt.
Das Konzept der Interface Polymorphie
Klingt zwar kompliziert ist aber eigentlich total entspannt.
Interface Polymorphie bedeutet, dass du eine Variable mit dem Namen deines Java Interfaces definieren kannst und eine Instanz von einer Klasse, die das Interface implementiert, dieser Variablen zuweisen kannst.
Über diese Instanz können wir anschließend alle im Interface definierten Methoden aufrufen.
Aber jetzt Schluss mit der Theorie schauen wir uns das ganze praktisch an unserem Webshop Beispiel an.
Wir haben ein Interface mit dem Namen IZahlung
definiert. Daher legen wir eine Variable zahlungsart
vom Typ IZahlung
an.
private IZahlung zahlungsart = null;
Nach dem Anlegen der Variable zahlungsart
hat diese jedoch den Wert null
.
Daherhaben wir als nächstes zu klären, wie wir der Variable zahlungsart
eine Instanz zuweisen.
Und wie du vielleicht schon richtig vermutest, machen wir das mit Hilfe eines Konstruktors und einer setter-Methode. Aber auch das hat wieder einen coolen Namen.
Setter- und Konstruktor Dependency Injection
Um dem Attribut zahlungsart beim Erzeugen der Webshop Instanz einen Wert zuweisen zu können, erweitern wir im ersten Schritt den Konstruktor um einen weiteren Parameter vom Typ unseres Java Interfaces IZahlung
.
public Webshop(String name,IZahlung iz) { this.name = name; this.zahlungsart = iz; }
Über den Parameter iz
können wir jetzt die IZahlungsart
Instanz bei Erstellung unseres Webshops initialisieren. Dieses Vorgehen nennen wir Konstruktor-Injection.
Des Weiteren erstellen wir eine setter-Methode, mit der wir die Zahlart auch noch nach Erzeugung der Webshop Instanz verändern können.
public void setZahlungsart(IZahlung zahlungsart) { this.zahlungsart = zahlungsart; }
Die setter-Methode erwartet als Parameter eine Instanz vom Typ IZahlung
, die wir dem Webshop-Attribut zahlungsart
zuweisen.
Jetzt fehlt nur noch eines!
Wir müssen noch die Möglichkeit schaffen die Interface-Methode erzeugeZahlung
aufzurufen.
Hierzu erstellen wir eine Methode zahlen()
, die nichts anderes macht als die erzeugeZahlung
Methode der zahlungsart
Instanz aufzurufen.
public void zahlen(){ zahlungsart.erzeugeZahlung(); }
Und erkennst du es?
Diese Methode ist völlig unabhängig von einer konkreten Zahlungsart Implementierung, ob nun mit PayPal, Kredit- oder EC-Karte bezahlt wird hängt nur davon ab, ob wir der zahlungsart
Instanz ein PayPal-, EC- oder Kreditkarten-Objekt über den Konstruktor bzw. die setter-Methode zugewiesen haben.
Wir haben also unser Ziel erreicht und die Logik der Zahlungsanbieter vollständig aus der Webshop-Klasse in externe Klassen verlagert.
Innerhalb der Webshop-Klasse steht jetzt nur noch die Aktion, nämlich die Zahlung, im Fokus. Auf welche Weise diese Zahlung erfolgt ist Aufgabe der Klassen, PayPal, EC und Kreditkarte.
Ein weiteres Beispiel zur Dependency Injection findest du im folgenden Video.
Testen wir unser Programm!
Okay, kommen wir zur Stunde der Wahrheit und testen unser Programm in dem wir unseren Webshop mit den unterschiedlichen Zahlarten betreiben.
1.) Wir erzeugen eine Instanz des Webshops und initialisieren die Zahlart mit der PayPal Zahlungsmethode.
Webshop w = new Webshop("Shop 1",new PayPal());
Lassen wir uns das einen Moment auf der Zunge zergehen und insbesondere einen Blick auf die Argumente des Konstruktors werfen.
Der erste Parameter ist noch unspektakulär. Hier setzen wir lediglich das Namensattribut des Webshops auf den Namen Shop 1.
Spannend ist der zweite Parameter. Hier übergeben wir eine neu erzeugte Instanz der PayPal Klasse und sehen den Interface Polymorphismus in Action.
Der zweite Parameter ist nämlich vom Typ unseres Java Interfaces IZahlung
und weil die Klasse PayPal dieses Interface implementiert ist der Typ IZahlung
mit dem Typ PayPal
kompatibel.
Als nächstes rufen wir die Methode zahlen()
der Webshop Instanz w
auf.
w.zahlen();
Da wir der Variable zahlungsart
eine PayPal Instanz zugewiesen haben, führt dies zu einem Aufruf der erzeugeZahlung
Implementierung aus der Klasse PayPal.
Aus diesem Grund erhalten wir die Bildschirmausgabe:
Zahlungslogik PayPal
Als nächstes wollen wir mit Hilfe der setter-Methode setZahlungsart
die Zahlungsart von PayPal auf Kreditkarten-Zahlung ändern.
Hierzu müssen wir der setter-Methode lediglich eine Kreditkarten-Instanz als Parameter übergeben.
w.setZahlungsart(new Kreditkarte());
Rufen wir jetzt die zahlen()
Methode der Webshop-Klasse auf, erhalten wir wie erwartet die Ausgabe:
Zahlungslogik Kreditkarte
Mit Hilfe der setter Methode setZahlungsart
können wir also dynamisch während der Laufzeit des Programms die Verarbeitungsart der Zahlung ändern.
Dies entspricht einem bekannten objektorientierten Design Muster. Nämlich dem sogenannten Strategy Pattern.
Fazit: In diesem Artikel haben wir uns angesehen wie wir die Implementierung und die Definition einer Funktionalität mit Hilfe von Java Interfaces trennen können. Mit Hilfe von Interface Polymorphie und Dependency Injection haben wir außerdem gesehen wie wir während der Laufzeit die Implementierung einer Funktionalität austauschen können.
Hat dir der Artikel gefallen? Dann folge uns doch gleich auf Facebook!
Clemens
16. Juli 2018 at 12:16Danke, sehr gute Erklärung! Einfach und verständlich, bitte weiter so
Kim Peter
8. September 2018 at 19:35Hallo Clemens, vielen Dank! Viele Grüße Kim
Ahmad
25. September 2018 at 9:10Hallo Kim,
ein fettes Dankeschön. Ich habe seit gestern viele deiner Artikels gelesen. Nun soll ich einen Weg finden, meine Kenntnisse zu vertiefen ^^
Lg,
Ahmad
Kim Peter
25. September 2018 at 9:39Hallo Ahmed, super! Vielen Dank! Beste Grüße Kim
Tahsin
8. Oktober 2018 at 22:54Super erklärt.
Danke KIM
Kim Peter
9. Oktober 2018 at 6:00Freut mich! Viele Grüße Kim
Hamudi
16. Oktober 2018 at 10:48war ganz ok. geht aber auch besser.
Kim Peter
16. Oktober 2018 at 11:02Hallo Hamudi, danke für dein Feedback! Das ist sicher richtig. Bin aber der der Überzeugung das machen immer besser ist als nicht machen. Viele Grüße Kim
Nicolas
13. April 2020 at 20:20Kommentar ganz ok, konstruktive Kritik wäre besser ;D
@HAMUDI
Chrissi
21. Dezember 2018 at 11:25Was ich nicht ganz verstehe:Wo ist dabei jetzt der vorteil zu einer abstrakten klasse Zahlung, mit drei klassen paypal, ec, kreditkarze die von dieser erben? Man könnte doch dann auch eine variable Zahlung zahlung = new 1 der drei machen. Und dann auf diese zahlungsart zugreifen, oder liege ich falsch?
Kim Peter
21. Dezember 2018 at 12:04Hallo Chrissi, ja, in vielen Fällen funktioniert sowohl eine Klasse als auch ein Interface. Vorteil von Interfaces sind das man mehr als eines je Klasse verwenden kann. Vorteil von abtrakten Klassen sind, dass man in diesen auch nicht abstrakte Methoden implementieren kann. Viele Grüße Kim
Christian S.
25. Juni 2019 at 7:27Mit Interfaces habe ich folgendes Problem: Alle Methoden müssen public sein (mal abgesehen von den private Methoden ab Java 9). Wenn ich im Team eine Library programmiere möchte ich gerne Schnittstellen definieren, die andere Teammitglieder implementieren sollen. Aber für die API-Nutzer sollen nicht alle Methoden public sein. Abstrakte Klassen ermöglichen im Gegensatz zu Interfaces genau das, daß man einige Methoden public setzen kann, andere package private. Was hältst du von dieser Vorgehensweise?
Kim Peter
2. Juli 2019 at 11:17Hallo Christian, ich persönlich halte private abstrakte Methoden für Overhead. Aber natürlich kann man das so machen. Viele Grüße Kim
Tobi
15. Januar 2019 at 9:13Unfassbar gut erklärt! Keine stumpfen Informationen trocken erklärt, sondern mit Humor näher gebracht. Respekt für diese Mühe!
Kim Peter
15. Januar 2019 at 9:22Ich danke dir sehr für dein Feedback!
Dieter
17. Januar 2019 at 12:31Die Erklärungen und Ausführungen sind erfreulich schlicht formuliert und sprachlich bzw. sprach-bildlich so eindringlich präsentiert, dass man schon ziemlich vernagelt sein muss, um da nichts zu raffen.
Außerdem werden keine „Selbstverständlichkeiten“ als bestens bekannt unterstellt.
Aber: der Wermutstropfen für mich als Schnellleser ist das nahezu völlige Fehlen von Interpunktionen jeglicher couleur.
Schade, denn die bremsen einen ja immer rechtzeitig vor den Kurven, sodass man nicht aus ihnen heraus fliegt – was mir latürnich „gerne“ passiert.
Kim Peter
17. Januar 2019 at 13:22Hallo Dieter, danke für das Feedback. Werde das bei Gelegenheit nochmal überarbeiten. Viele Grüße Kim
Fabian
9. Februar 2019 at 19:17Hi echt super erklärt, danke sehr. 🙂
Kim Peter
9. Februar 2019 at 19:35Hi Fabian, ich danke dir! Viele Grüße Kim
Artur
25. Mai 2019 at 21:49sehr gut erklärt. Weiter so!
Kim Peter
2. Juli 2019 at 11:25Hallo Artur, dankeschön! Viele Grüße kim
Volker
26. Juni 2019 at 10:36Hallo,
ich gebe den anderen Kommentatoren recht, gut und verständlich erklärt, wozu Interfaces.
Allerdings sehe ich nicht die Lösung zu der am Beginn gestellten Frage „Was machen wir, wenn beispielsweise PayPal beschließt ihre Implementierungslogik zu ändern?“
Statt der Klasse Webshop muss nun die Klasse PayPal geändert werden…
Abgesehen davon, wird beim „echten“ Aufruf von PayPal dort ein „echtes“ Interface angesprochen werden.
Also wo ist der Vorteil?
Danke und Gruß
Volker
Kim Peter
2. Juli 2019 at 11:11Hallo Volker, das Interface definiert den Input und den Output. Und das ist alles was uns interessiert. Wie die konkrete Implementierung ausschaut, also wie konkret der Input in den Output transformiert wird kann uns egal sein. Viele Grüße Kim
Andreas Ortner
12. März 2020 at 7:14Sehr anschaulich erklärt
Kim Peter
25. März 2020 at 7:55Danke für das Feedback!
Choose a style: