Das Strategy Pattern und die Java Comparator Schnittstelle. Ein guter Grund Interfaces zu verwenden!

Keylearnings:

  • Das Strategy Pattern in Action!
  • Wie Java Interfaces die Kommunikation zwischen Objekten regeln.
  • Wie du Objekte miteinander vergleichst.
  • Wie du die Java Comparator Schnittstelle verwendest.
  • Wie du die Methoden der Collections Klasse für deine Zwecke verwendest.

Manchmal erkennt man den Roh-Diamanten erst auf den zweiten Blick.

So ist es auch bei den Java Interfaces!

Ein Interface selbst kann nicht viel!

Abgesehen von Konstanten, können wir darin lediglich abstrakte und statische Methoden definieren.

Das haut erstmal niemanden vom Hocker!

Also warum sind Java Interfaces so nützlich?

Genau das wollen wir in diesem Artikel klären. Hierbei lernen wir eine wichtige Anwendung von Java Interfaces kennen. Nämlich das sogenannte Strategy Pattern (Entwurfsmuster).

Interfaces als Vertrag zwischen Objekten

Wie wir bereits gelernt haben kommunizieren, innerhalb eines objektorientierten Softwaresystems, Objekte miteinander.

Und genau hier haben Java Interfaces ihren großen Auftritt.

Mit Hilfe von Schnittstellen legen wir die Kanäle fest, über welche die Objekte Nachrichten untereinander austauschen.

Der Stecker muss in die Buchse passen

Um hiervon eine Vorstellung zu bekommen, lehnen wir uns an Beispiele der realen Welt an. Nämlich:

  1. Der Zapfhahn an der Tankstelle, der in das Auto passt.
  2. Die Kaffeekanne, die unter der Kaffeemaschine steht.
  3. Der Bohrer einer Bohrmaschine, der in die Bohrvorrichtung passt.

Okay, schauen wir uns das Beispiel der Bohrmaschine mal genauer an.

Für eine Bohrmaschine gibt es unterschiedliche Bohrer, einen für Beton, einen für Holz und einen weiteren für Metall.

Strategy Pattern

Abhängig von dem Ziel, das du verfolgst, musst du also den passenden Bohrer in die Vorrichtung der Bohrmaschine schrauben.

Genau wie die Klassen eines Java Programmes nicht vom selben Hersteller kommen müssen, sind auch Bohrer und Bohrmaschine nicht zwingend aus der selben Hand.

Damit die Komponenten dennoch problemlos miteinander funktionieren, müssen sich die Hersteller auf einen gemeinsamen Standard einigen.

In unseren Java Programmen erreichen wir diese Einigung mit Hilfe von Java Interfaces. Ein gutes Beispiel hierfür ist die Sortierung einer Liste nach unterschiedlichen Kriterien.

Die Comparator Schnittstelle

Bestimmt kennst du diese Reiseportale, in denen du deinen Urlaub buchen kannst.

Auf diesen Seiten wählst du dein gewünschtes Urlaubsziel aus und erhälst anschließend eine Liste von Vorschlägen.

Diese Vorschlagsliste kannst du nach vorgegebenen Kriterien sortieren lassen.

Strategy Pattern

Beispielsweise kannst du die Liste nach dem Gesamtpreis, oder nach den Hotels mit den meisten Weiterempfehlungen sortieren lassen.

Wie ist so etwas realisiert?

Die naheliegendste Möglichkeit ist es für jede Sortierreihenfolge einen eigenen Algorithmus zu schreiben.

Wir könnten beispielsweise vier Versionen des Insertion Sort Algorithmus implementieren.

Eine für die Sortierung nach dem Gesamtpreis, eine zweite für die Bewertung, eine dritte für die Sortierung nach der Weiterempfehlung und schließlich eine letzte, die nach den beliebtesten Hotels sortiert.

Da der Insertion Sort Algorithmus in jeder Sortiervariante auf gleiche Weise funktioniert, ist klar dass wir auf diese Weise einen Haufen doppelten Code erzeugen.

Aber gerade das wollen wir in der objektorientierten Programmierung vermeiden.

Orientieren wir uns doch lieber an unserem Bohrmaschinen Bohrer Beispiel!

Wie wäre es wenn wir den Sortieralgorithmus wie unsere Bohrmaschine benutzen und die Sortierkriterien ja nach bedarf, genau wie einen Bohrer an die Bohrmaschinen-Vorrichtung, an den Sortieralgorithmus andocken würden.

Jepp, genau das geht! Und diese Vorgehensweise wird Strategy Pattern genannt.

Das Strategy Pattern

Beginnen wir damit uns eine Basis aufzubauen und erstellen eine Klasse für ein Reiseangebot.

1: public class ReiseAngebot {
	
2:  private String hotelName = null;
3:  private int preis = 0;
4:  private int bewertungProzent = 0;
5:  private int bisherGebucht = 0;
	
6:  public ReiseAngebot(String hotelName, int preis, int bewertungProzent,
			int bisherGebucht) {
7:	this.hotelName = hotelName;
8:	this.preis = preis;
9:	this.bewertungProzent = bewertungProzent;
10:	this.bisherGebucht = bisherGebucht;
11:  }

12:  @Override
13:  public String toString() {
14:	return "ReiseAngebot [hotelName=" + hotelName + "]";
15:  }

16:  public String getHotelName() {
19:	return hotelName;
18:  }

19:  public int getPreis() {
20:	return preis;
21:  }

22:  public int getBewertungProzent() {
23:	return bewertungProzent;
24:  }

25:  public int getBisherGebucht() {
26:	return bisherGebucht;
27:  }
	
28:}

Die Klasse Reiseangebot verwenden wir zur Repräsentation eines einzelnen Angebots.

In der Klasse sind folgende vier Attribute enthalten:

  1. Den Namen des Hotels.
  2. Den Preis des Angebots.
  3. Die  Bewertung des Angebots
  4. Wie oft das Angebot bereits gebucht wurde.

Die Attribute Zwei bis Vier möchten wir als Sortierkriterium verwenden.

Alle Attribute lassen sich über den Konstruktor der Klasse initialisieren.

Außerdem haben wir die toString() Methode überschrieben, um den Namen des Hotels auf dem Bildschirm ausgeben zu können.

Des Weiteren sind, damit wir auch von außen auf die privaten Attribute zugriff haben, entsprechende getter Methoden in der Klasse implementiert.

Um eine Datenbasis für einen Test zu haben, legen wir vier Reiseangebote an und speichern diese in einer ArrayList.

ArrayList angebote = new ArrayList();
angebote.add(new ReiseAngebot("Alondra",300,80,1200));
angebote.add(new ReiseAngebot("Playa",500,85,900));
angebote.add(new ReiseAngebot("Paradis",450,75,2000));
angebote.add(new ReiseAngebot("Luxus Tempel",2000,100,500));

Unser Bohrer, die Comparator Schnittstelle!

Um unsere Angebote sortieren zu können, brauchen wir ein Kriterium, mit dem wir entscheiden können, ob ein Angebot x vor einem Angebot y einzusortieren ist oder eben nicht.

Und genau dies zu definieren ist Aufgabe des Java Comparator Interfaces!

Da wir nach insgesamt vier unterschiedlichen Kriterien sortieren wollen, müssen wir vier Klassen definieren, die das Java Comparator Interface implementieren.

Beginnen wir mit der Sortierung nach dem Preis!

Hierfür schreiben wir eine Klasse Preisvergleich, welche das Comparator Interface implementiert. Hier der Quellcode eine Erklärung folgt direkt im Anschluss.

1: public class PreisVergleich implements Comparator {

2:	@Override
3:	public int compare(Object a1, Object a2) {
4:		return ((ReiseAngebot) a1).getPreis()-((ReiseAngebot) a2).getPreis();				
5:	}
	
6:}

Wichtig ist gleich Zeile Eins. Hier verwenden wir das Schlüsselwort implements um das Java Comparator Interface in die Klasse einzubinden.

Die Comparator Schnittstelle definiert die Methode compare, welche als Parameter die zu vergleichenden Objekte (in unserem Beispiel Instanzen der Klasse Reiseangebot) erwartet und einen Integer-Wert zurückliefert.

Innerhalb der compare Methode wird die Differenz zwischen dem Preis des ersten und des zweiten Angebots berechnet. Da die Übergabeparameter vom Typ Object sind, dürfen wir nicht vergessen die Instanzen a1 und a2 in den Typ Reiseangebot zu casten (Zeile 4).

Ist das Ergebnis dieser Berechnung negativ, bedeutet das, dass der Preis von Angebot 2 höher ist als der Preis von Angebot 1.

Haben beide Angebote den gleichen Preis, dann liefert die compare Methode den Wert Null.

Ist schließlich Angebot 1 teurer als Angebot 2, so ist das Ergebnis der Methode positiv.

Auf diese Weise haben wir die Möglichkeit zwei Reiseangebote in Bezug auf den Preis miteinander zu vergleichen.

Natürlich benötigen wir auch für die anderen Sortierkriterien analoge Comparator-Klassen. Diese Klassen unterscheiden sich einzig und allein in dem Aufruf der getter Methoden.

Im folgenden die entsprechende Comparator-Klasse für die Sortierung nach der Bewertung des Angebots.

1: public class BewertungVergleich implements Comparator{
	
2:  @Override
3:  public int compare(Object a1, Object a2) {
4:	return ((ReiseAngebot) a1).getBewertungProzent()-((ReiseAngebot) a2).getBewertungProzent();				
5:  }
	
6: }

Und Last but not Least die Comparator-Klasse für die Sortierung nach den am meisten gebuchten Angeboten!

1: public class AmMeistenGebuchtVergleich implements Comparator{
	
2:  @Override
3:  public int compare(Object a1, Object a2) {
4:	return ((ReiseAngebot) a1).getBisherGebucht()-((ReiseAngebot) a2).getBisherGebucht();				
5:  }

6:}

Hiermit haben wir unsere Bohrer erzeugt! Wie bekommen wir diese jetzt in die Bohrmaschine um damit eine nach dem gewünschten Kriterium sortierte Liste zu erhalten?

Genau jetzt betritt das Strategy Pattern die Bühne!

Java Collections Klasse

Die Klasse Collections

Darf ich vorstellen unsere Bohrmaschinenvorrichtung! Die Klasse Collections.

Die Java Klasse Collections enthält eine Menge nützlicher Funktionen. Unter anderem auch eine sort Methode, in der ein Sortieralgorithmus implementiert ist.

Und weißt du was das Beste daran ist? Die Methode erwartet als ersten Übergabeparameter eine Liste und als zweiten Parameter eine Comparator-Instanz.

Da wir die Reiseangebote in einer ArrayList gespeichert haben und alle unsere Comparator-Klassen das Java Comparator Interface implementieren, können wir die sort Methode also mit unseren Zutaten aufrufen.

Okay, ich zeigs dir! Wenn wir unsere Liste nach dem Angebotspreis sortieren wollen, müssen wir die sort Methode wie folgt aufrufen.

Collections.sort(angebote,new PreisVergleich());

Bei dem ersten Übergabeparameter angebote handelt es sich um die ArrayList, die unsere Testdaten enthält. Der zweite Parameter ist eine Instanz der PreisVergleich Comparator-Klasse.

Schauen wir uns das Ergebnis an, in dem wir über die ArrayList iterieren und den Inhalt jedes Eintrags auf dem Bildschirm ausgeben.

for(ReiseAngebot ra : angebote){
    System.out.println(ra.toString());
}

Die Bildschirmausgabe dieses Code-Schnipsel ist:

ReiseAngebot [hotelName=Alondra]
ReiseAngebot [hotelName=Paradis]
ReiseAngebot [hotelName=Playa]
ReiseAngebot [hotelName=Luxus Tempel]

Vergleichen wir das mit unseren Testdaten von oben, dann erkennen wir, dass das Hotel Alondra in der Tat das günstigste Angebot und der „Luxus Tempel“ am teuersten ist.

Unsere Liste ist also korrekt! Die Angebote sind preislich aufsteigend sortiert.

So jetzt die naheliegende Quizfrage: „Was müssen wir ändern damit wir eine Liste sortiert nach der Anzahl der Buchungen erhalten?“

Die eine Millionen Euro Antwort lautet: „Wir müssen bei dem Aufruf der Java Collections sort Methode die Instanz der PreisVergleich Klasse durch die Instanz einer AmMeistenGebuchtVergleich Klasse ersetzen.

Collections.sort(angebote,new AmMeistenGebuchtVergleich());

Iterieren wir jetzt über die ArrayList und geben jedes Element auf dem Bildschirm aus, dann erhalten wir:

ReiseAngebot [hotelName=Luxus Tempel]
ReiseAngebot [hotelName=Playa]
ReiseAngebot [hotelName=Alondra]
ReiseAngebot [hotelName=Paradis]

Und in der Tat wurde der Luxus Tempel am seltesten und das Hotel Paradis am häufigsten gebucht. Die Angebotsliste ist jetzt also aufsteigend nach der Anzahl der Buchungen sortiert.

Strategy Pattern Demo!

Wo hat sich das Strategy Pattern versteckt?

Lass uns zur Demonstration des Strategy Pattern ein kleines Testprogramm schreiben, das den Benutzer fragt nach welchem Kriterium die Angebote sortiert werden sollen und anschließend die sortierte Liste auf dem Bildschirm ausgibt!

1: System.out.println("Wie sollen die Angebote sortiert werden?");
2: System.out.println("1 für Preis, 2 für Bewertung, 3 für Anzahl Buchungen");
		
3: Scanner scanner = new Scanner(System.in);
4: int auswahl = scanner.nextInt();
		
5: switch(auswahl){
6:	case 1: Collections.sort(angebote,new PreisVergleich());
7:		break;
8:	case 2: Collections.sort(angebote,new BewertungVergleich());
9:		break;
10:	case 3: Collections.sort(angebote,new AmMeistenGebuchtVergleich());
11:		break;
12:	}
		
		
13:	for(ReiseAngebot ra : angebote){
14:	    System.out.println(ra.toString());
15:	}

Was passiert hier?

Um zu entscheiden nach welchem Kriterium die Liste der Angebote sortiert werden soll, bitten wir in den ersten beiden Zeilen den Anwender darum eine Zahl zwischen Eins und Drei einzugeben.

Die Eingabe lesen wir anschließend mit Hilfe eines Scanners (Zeile 4) in die Integer-Variable auswahl ein.

In der switch Anweisung in den Zeilen Fünf bis Zwölf sehen wir das Strategy Pattern im Einsatz.

Um die Liste der Angebote nach den Wünschen des Anwenders zu sortieren, docken wir, während der Laufzeit des Programms, die notwendige Comparator-Klasse an die sort Methode aus der Collections Klasse an.

Anschließend geben wir in den Zeilen 13-15 die sortierte Liste auf dem Bildschirm aus.

Fazit: In diesem Artikel hast du einen guten Grund kennengelernt, weshalb du Java Interfaces einsetzen solltest. Nämlich das Strategy Pattern. Mit Hilfe dieses Patterns kannst du während der Programmlaufzeit aus einer Menge von Implementierungen auswählen. Als Beispiel hierfür hast du in diesem Artikel das Comparator Interface kennengelernt.

Ich freue mich auf deine Fragen im Kommentarbereich!

Hat dir der Artikel gefallen? Dann folge uns doch am besten gleich auf Facebook!

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

Kommentare (0)

Hinterlasse ein Kommentar