JAVA Interfaces! Warum, weshalb und wieso?

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.

Design Prinzip Java Interfaces

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!

Hallo ich bin Kim und ich möchte ein großer Programmierer werden. Machst du mit?

Kommentare (25)

  • Antworte

    Danke, sehr gute Erklärung! Einfach und verständlich, bitte weiter so

    • Hallo Clemens, vielen Dank! Viele Grüße Kim

  • Antworte

    Hallo 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

    • Hallo Ahmed, super! Vielen Dank! Beste Grüße Kim

  • Antworte

    Super erklärt.
    Danke KIM

    • Freut mich! Viele Grüße Kim

  • Antworte

    war ganz ok. geht aber auch besser.

    • Hallo 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

    • Kommentar ganz ok, konstruktive Kritik wäre besser ;D
      @HAMUDI

  • Antworte

    Was 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?

    • Hallo 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

    • Mit 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?

    • Hallo Christian, ich persönlich halte private abstrakte Methoden für Overhead. Aber natürlich kann man das so machen. Viele Grüße Kim

  • Antworte

    Unfassbar gut erklärt! Keine stumpfen Informationen trocken erklärt, sondern mit Humor näher gebracht. Respekt für diese Mühe!

    • Ich danke dir sehr für dein Feedback!

  • Antworte

    Die 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.

    • Hallo Dieter, danke für das Feedback. Werde das bei Gelegenheit nochmal überarbeiten. Viele Grüße Kim

  • Antworte

    Hi echt super erklärt, danke sehr. 🙂

    • Hi Fabian, ich danke dir! Viele Grüße Kim

  • Antworte

    sehr gut erklärt. Weiter so!

    • Hallo Artur, dankeschön! Viele Grüße kim

  • Antworte

    Hallo,
    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

    • Hallo 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

  • Antworte

    Sehr anschaulich erklärt

    • Danke für das Feedback!

Hinterlasse ein Kommentar