Die Vier Säulen der Objektorientierung

Unter den 4 Säulen der Objektorientierung versteht man folgende Paradigmen:

Beziehungen

Objekte und Klassen existieren nicht für sich alleine, sondern stehen in Beziehungen zueinander

  • sie haben Gemeinsamkeiten (Generalisierung/Spezialisierung)
  • sie enhalten einander (Aggregation/Komposition)

Kapselung

Unter Kapselung versteht man den Schutz von Klassen und Variablen. Jedes Objekt ist von einer Kapsel umgeben und von außen sind nur die erwünschten Methoden und Variablen ersichtlich.

Vererbung

Eine Klasse kann auf eine Unterklasse vererbt werden. Die Unterklasse übernimmt die Eigenschaften der Elternklasse und kann diese erweitern oder auch einschränken. Ein Beispiel wäre die Elternklasse Fahrzeug mit der Unterklasse Auto. Jedes Auto ist ein Fahrzeug, aber nicht jedes Fahrzeug ein Auto.

Polymorphie

Dank Polymorphie ist es möglich, dass mehrere Unterklassen die selbe Botschaft verstehen aber unterschiedlich damit umgehen. So ist sowohl das Fahrrad sowie das Auto ein Fahrzeug und besitzen beide die Methode bremsen() auch sind die Resultate die selben, das Fahrzeug wird langsamer, jedoch können beide Methoden komplett unterschiedlich implementiert sein.

Unterschied Set, List und Map

Jeder Entwickler hat sie schon einmal verwendet Lists, Sets und Maps. Aber wo genau Unterscheiden sie sich und wann sollte ich was verwenden?

Kurzgesagt sind bei einer List die Elemente indexiert von 1-n. Bei Sets gibt es keine Ordnung weil jedes Element nur genau einmal vorkommen darf. Bei einer Map gibt es zu jeder Value einen einzigartigen Key.

Immer wenn ich mit mehreren Objekten der selben Klasse zu tun haben wird diese in eine Collection zusammengefasst.

Eine Collection zeigt eine Gruppe von Objekten die sogenannten Elemente.  Um zu entscheiden welche Art ich für meinen konkreten Fall verwenden sollte müssen folgende Fragen zuerst beantwortet werden:

Erlaube ich Mehrfacheinträge?

 Bei einer List Collection sind doppelte Einträge erlaubt. Es kann eine beliebige Anzahl von selben Objekten in die Liste hinzugefügt werden ohne die vorhandenen Werte und Keys zu beeinflussen. In einen Set ist es nicht erlaubt, dass das gleiche Objekt ein zweites Mal vorkommt. Bei einer Map darf die Value öfters vorkommen, solange der Key ein anderer ist.

Dürfen Listenelemente auch Null sein?

In einer List dürfen beliebig oft Null Werte vorkommen. Bei einem Set darf genau ein einziges Mal Null vorkommen. Bei einem Set darf der Key einmal Null sein , die Value darf beliebig oft Null sein.

Sind meine Elemente geordnet?

List und all ihre Unterarten behalten die eingefügte Ordnung. Set speichert die Objekte in keiner bestimmen Ordnung. Eine Map an und für sich auch nicht, jedoch gibt es spezielle Unterarten wie TreeMap welche die Objekte nach dem Key Wert sortiert.

Bekannte Unterklassen:

ArrayList Implementiert Listen-Funktionalität durch die Abbildung auf ein Feld; implementiert die Schnittstelle List.
LinkedList LinkedList ist eine doppelt verkettete Liste, also eine Liste von Einträgen mit einer Referenz auf den jeweiligen Nachfolger und Vorgänger. Das ist nützlich beim Einfügen und Löschen von Elementen an beliebigen Stellen innerhalb der Liste.
HashSet Eine Implementierung der Schnittstelle Set durch ein schnelles Hash-Verfahren.
TreeSet Implementierung von Set durch einen Baum, der alle Elemente sortiert hält.
LinkedHashSet Eine schnelle Mengen-Implementierung, die sich parallel auch die Reihenfolge der eingefügten Elemente merkt.
HashMap Implementiert einen assoziativen Speicher durch ein Hash-Verfahren.
TreeMap Exemplare dieser Klasse halten ihre Elemente in einem Binärbaum sortiert; implementiert NavigableMap.
LinkedHashMap Ein schneller Assoziativspeicher, der sich parallel auch die Reihenfolge der eingefügten Elemente merkt.
WeakHashMap Verwaltet Elemente mit schwachen Referenzen, sodass die Laufzeitumgebung bei Speicherknappheit Elemente entfernen kann.

Anwendungsgebiete

  • Wenn ein Eintrag nur einmal vorkommen darf empfiehlt es sich Sets zu verwenden
  • Wenn die List öfter dursucht wird, verwendet man Lists (ArrayList)
  • Wenn die Reihenfolge wichtig ist, ist List zu preferieren
  • Wenn die Datenbank ein Key Value Ordnung benötigt sind Maps die beste Wahl

 

Model-View-Controller Pattern

Das Model-View-Controller Pattern wird öfters auch mit MVC abgekürzt. Jede Einheit hat klar definierte Aufgaben. Das Model enthält die Daten, die View stellt diese dar und der Controller vermittelt zwischen den beiden Einheiten.

Eingeführt wurde dieser Patterns bei grafischen Anwendungen für Desktop Applikationen. In den letzten Jahren ist es aber auch bei Web Applikation sehr populär geworden.

Ein bekanntes Beispiel für ein Web Framework welches mit MVC arbeitet ist CakePHP.

Ein einfaches Beispiel wäre ein Benutzerprofil. In einer Tabelle werden Name, E-Mail und Passwort eines Users gespeichert. Der User sieht auf einer Webseite seine Daten und kann diese auch bearbeiten. Diese Aufgabe übernimmt die View. Wenn der User seine Änderungen speichern will überprüft der Controller ob sich die Benutzerdaten geändert haben, falls ja übergibt er die Daten dem Model welche den Datenbankeintrag aktualisiert.

Drawing1

Der Vorteil liegt daran, dass die Dateien komplett unabhängig von ihrer Darstellung sind. Das Benutzerprofil könnte über eine Webseite oder über ein Mobile App bearbeitet werden, der Controller definiert die Schnittstelle und das Model bleibt unverändert.

In der Java Welt fällt es öfters schwer Controller und View voneinander zu trennen weil diese eng miteinander verbunden sind. Deswegen spricht man hier öfters von einen Delegate.

 

Observer Pattern

Die Definition dieses Patterns lautet:

„Definiere eine 1-zu-n-Abhängigkeit zwischen Objekten, so dass die Änderung des
Zustands eines Objekts dazu führt, dass alle abhängigen Objekte benachrichtigt
und automatisch aktualisiert werden.“

Stellen sie sich eine Party vor. Auf dieser Party gibt es 2 Gruppen von Gästen. Die einen die aktiv Erzählen und die anderen die passiv zuhören. Die Erzähler (Observable) interessiert es nicht wer ihnen zuhört, sie schweigen aber wenn sie gar keinen Zuhörer (Observer) haben. Die Zuhörer reagieren auf Witze der Unterhalter.

Realisierung

Um einen Erzähler zu realisieren bietet sich die Klasse Observable an. Diese verfügt über die beiden Methoden setChanged() und notifyObservers(). Mit setChanged() wird die Änderung angekündigt, und mit notifyObservers() wird sie tatsächlich übermittelt.

Würde es keine Änderungen geben, würde notifyObservers auch nichts übertragen.

Wir stellen jetzt unser Party-Szenario nach. Zuerst schreiben wir eine Klasse namens JokeTeller. Die erstellten Objekte erzählen einen Witz und verschicken sie an alle Zuhörer.

class JokeTeller extends Observable{
  private static final List() jokes = Arrays.asList(
    "Sorry, aber du siehst so aus, wie ich mich fühle.",
    "Eine Null kann ein bestehendes Problem verzehnfachen.",
    "Wer zuletzt lacht, hat es nicht eher begriffen.",
    "Wer zuletzt lacht, stirbt wenigstens fröhlich.",
    "Unsere Luft hat einen Vorteil: Man sieht, was man einatmet."
  );

  public void tellJoke(){
    setChanged();
    Collections.shuffle( jokes );
    notifyObservers( jokes.get(0) );
  }
}

setChanged() setzt intern eine Flag. Nach dem Aufruf der notifyObservers() Methode wird diese wieder gelöscht.

Interessierte Zuhörer (Beobachter) müssen sich am Observable-Objekt mit der Methode addObserver anmelden. Gültige Objekte sind aber nur jene, welche die Schnittstelle Observer implementieren. Mit der Methode deleteObserver(Observer) können sie sich wieder abmelden.

Die Namensgebung der Oberklasse ist leider etwas unglücklich gewählt. Normalerweise drückt „able“ immer ein Schnittstelle aus. In diesen Fall ist aber Observable eine echte Klasse während Observer eine Schnittstelle bezeichnet.

Die Observer Schnittstelle

Als nächstes folgt die Realisierung der Zuhörer Klasse. Die hat die Schnittstelle Observer implementiert. Ihre Aufgabe besteht darin auf die Witze des Erzählers zu reagieren. Als Attribute besitzt es nur einen Namen.

class JokeListener implements Observer{
  final private String name;

  JokeListener( String name ){
    this.name = name;
  }

  @Override public void update( Observable o, Object arg ){
    System.out.println( name + " lacht über: \"" + arg + "\"" );
  }
}

Die Party kann beginnen

Um unser Beispiel zu beenden erstellen wir jetzt eine Party Klasse und lassen unser Partygäste miteinander agieren.


public class Party
{
  public static void main( String[] args )
  {
    Observer achim    = new JokeListener( "Achim" );
    Observer michael  = new JokeListener( "Michael" );
    JokeTeller chris  = new JokeTeller();

    chris.addObserver( achim );

    chris.tellJoke();
    chris.tellJoke();

    chris.addObserver( michael );

    chris.tellJoke();

    chris.deleteObserver( achim );

    chris.tellJoke();
  }
}

Anwendung

Das Observer Pattern findet sich in fast allen Grafischen Oberflächen wieder. In der Java Welt werden sie als Listener bezeichnet. Besten Beispiel ist ein einfacher Button.

Von Natur aus ist nicht definiert was passieren soll wenn dieser gedrückt wird. Ein EventListener wird an den Button angehängt, der reagieren soll sobald ein bestimmtes Ereignis eintritt. In unseren einfachen Fall wenn der Button betätigt wird.

Zusammenfassung

  • Observer findet immer dann Anwendung, wenn sich der Status eines Objekts ändert und beliebig viele weitere Objeke darüber informiert werden
  • Es gibt Ereignisquellen und Beobachter
  • Beobachter müssen austauschbar sein

Template Method Pattern

Die Definition dieses Templates lautet:

„Definiere das Skelett eines Algorithmus in einer Operation und delegiere einzelne Schritte an Unterklassen. Die Verwendung einer Schablonenmethode ermöglicht es Unterklassen, bestimmte Schritte eines Algorithmus zu überschreiben, ohne seine Struktur zu verändern.“

Das Template Method Pattern oder auch auf Deutsch Schablonenmethode gehört zu Kategorie der Behavioral Design Patterns. In der abstrakten Parent Klasse wird die Struktur der Methoden definiert. Die genaue Funktionsweise wird aber erst in den dazugehörigen Unterklassen definiert.

Realisierung

Ein einfaches Beispiel lautet wie folgt, ein Programm liest einen eingegeben Text und modifiziert ihn. Eine Methode gibt den Text nur in Großbuchstaben aus während im anderen Fall nur Kleinbuchstaben zurückgegeben werden.

Wir erstellen eine abstrakte Klasse mit dem Namen Eingabe. Die abstrakte Klasse ist nur für das einlesen und auslesen des Textes verantwortlich. Das bearbeiten übernimmt eine Unterklasse.


public abstract class Eingabe{
	public final void run(){
		String eingabe = textEingeben();
		String konvertiert = convert(eingabe);
		drucke(konvertiert);
	}
	private final String textEingeben(){
		final String MESSAGE = "Bitte geben Sie den Text ein: ";
		return JOptionPane.showInputDialog(MESSAGE);
	}	
	protected abstract String convert(String eingabe);
	private final void drucke(String text)
	{
		System.out.println(text);
	}
}

Jetzt werden 2 Unterklassen definiert, wo deklariert ist  auf welche Art und Weise der Text bearbeitet wird. Die Klasse UppercaseConverter wandelt den Text in Großbuchstaben um, während die Klasse LowercaseConverter den Text in Kleinbuchstaben ändert.

Die beiden Klassen unterscheiden sich nur minimal:

LowercaseConverter:

public class LowercaseConverter extends Eingabe{
	@Override
	protected String convert(String eingabe){
		return eingabe.toLowerCase();
	}
}

UppercaseConverter:

public class UppercaseConverter extends Eingabe{
	@Override
	protected String convert(String eingabe){
		return eingabe.toUpperCase();
	}
}

In der Oberklasse ist der Algorithmus definiert und die Oberklasse ruft die entsprechende Methode in der Unterklasse auf. Dieses Verhalten wird auch als das Hollywood-Prinzip bezeichnet.

Hollywood-Prinzip

Rufen Sie uns nicht an – wir rufen Sie an!

Dank des Hollywood Prinzipes kann der Client leicht den gewünschten Converter auswählen:

public class Client{
	public static void main(String[] args){
		Eingabe eingabe = new LowercaseConverter();
		eingabe.run();
		Eingabe neueEingabe = new UppercaseConverter();
		neueEingabe.run();
	}
}	

Zusammenfassung

  • Definition einen Algorithmus
  • Teile des Algorithmus werden von der Oberklasse selbst ausgeführt, andere Teile werden von Subklassen vorgeschrieben, die die nicht abstrakten Teile implementieren
  • Den Algorithmus beschreiben Sie in einer finalen Methode der abstrakten Oberklasse