B) Command-Pattern und Observer-Pattern

Das Command- und das Observer-Pattern sind sehr ähnliche Muster, weswegen sie hier gemeinsam erklärt werden. Beide basieren auf dem Prinzip der Polypmorphie und mit ihnen ist es möglich, dass ein Programmteil einen anderen aufruft, ohne zu wissen, was genau passieren wird, aber der Aufrufer kann sich darauf verlassen, dass etwas passiert. Dabei ist das Observer-Pattern eigentlich nur die Fortführung des Command-Patterns.

Die Problemstellung

Das Command-Pattern lässt sich ziehmlich gut mit dem Aufruf eines Programmes mit Kommandozeilenparametern erklären. Mit diesen Parametern kann das Verhalten eines Programmes von Beginn an festgelegt werden. So gibt der Befehl java -version die Java-Version aus, anstatt ein Programm auszuführen. In der Regel werden Parameter durch Leerzeichen getrennt. Allgemeiner lässt es sich nicht sagen, da es hier grundlegende Unterschiede zwsichen den Betriebsystemen gibt, sowie verschiedene Implementationen der einzelnen Programme.
In diesem Beispiel sollen also alle Parameter durch ein Leerzeichen getrennt sein. Diese Trennung übernimmt die JVM schon für uns, wir bekommen die einzelnen Parameter im Array args[] in der main-Methode übergeben.
Wir wollen für unser Programm drei Parameter für die Kommandozeile bereit stellen:

  • -version: Soll die Programmversion ausgeben.
  • -help: Soll einen kurzen Hilfetext ausgeben.
  • -gui: Soll die grafische Oberfläche des Programmes starten.

Die Implementation

Ihre Herangehensweise an dieses Problem bis jetzt wäre vermutlich, eine Switch Case Anweisung zu verwenden. Diese Anweisungen sind alles andere als objektorientiert, sie sind vielmehr ein Relikt aus alten Zeiten, welches in Java aufgenommen wurde, um es den Anfängern zu erleichtern. Als Hauptgrund gegen Switch Case-Anweisungen wird aufgeführt, dass das obligatorische break ein verschönertes GOTO sei. Auch wenn dies nicht ihre Meinung ist, ist es sinnvoll, wenigstens das Command-Pattern zu erkennnen.

GOTO ist ein Befehl zum Springen innerhalb des Programms auf sogenannte Sprungmarken. Schon durch geringe Anwendung dieses Befehls wird der Code unübersichtlich. Dann wird er Spaghetti-Code genannt. Der Befehl GOTO und alle Sprachen, die ihn verwenden, sind bei erfahreneren Programmieren sehr verpöhnt.

Zur Implementierung brauchen wir zuerst eine Schnittstelle, die definiert, was der jeweilige Kommandozeilenbefehl alles können soll:

package de.jbb.commandpattern;

public interface Command{
  
  public void execute();

}

Von unserem Befehl wird nur verlangt, dass er ausführbar ist. Nun müssen alle Befehlsklassen eben diesen Command implementieren. Wir haben drei Befehle, also brauchen wir drei Unterklassen:

package de.jbb.commandpattern;

public class HelpCommand implements Command{

  @Override public void execute(){
    System.out.printlin("Das ist die Programmhilfe");
  }

}
package de.jbb.commandpattern;

public class VersionCommand implements Command{
  
  @Override public void execute(){
    System.out.println("Version 1.00");
  }

}
package de.jbb.commandpattern;

public class GUICommand implements Command{
  
  @Override public void execute(){
    javax.swing.JOptionPane.showMessageDialog(null,"Das ist die tolle GUI");
  }

}

Nun müssen wir unsere Befehle in der main-Methode noch in einer HashMap zusammenfassen:

package de.jbb.commandpattern;

public void Program{

  public static void main(String args[]){
    HashMap<String,Command> map = new HashMap<String, Command>();
    map.put("-help", new HelpCommand());
    map.put("-gui", new GUICommand())
    map.put("-version", new VersionCommand());
  }

}

Das Commandpattern muss nicht unbedingt mit einer Map oder einer Liste realisiert werden. Der eigentliche Aspekt des Command-Patterns kommt jetzt. Sie können den Befehl aufrufen, ohne zu wissen, was er genau tut, ja ohne zu wissen, was er genau ist. In unserem Fall müssen wir jetzt jeden Kommdozeilenparameter an die HashMap übergeben und den resultierenden Befehl ausführen:

for(String cmd: args){
  Command command = map.get(cmd);
  if(command!=null){
    command.execute();
  }
}

Die Nachteile

Natürlich hat auch das Command-Pattern Nachteile. Der Größte ist wahrscheinlich, dass bei vielen Befehlen auch viele Klassen benötigt werden. Außerdem müssen alle Befehle mit der gleichen Information zurecht kommen, das heißt, Sie können zwar der execute-Methode noch Parameter hinzufügen. Diese müssen dann aber auch alle anderen Unterklassen haben, auch wenn nur ein Befehl diesen Parameter wirklich braucht. Sie haben keine Chance, die Parameterübergabe an jeden Befehl einzeln anzupassen.

Die Weiterführung: Das Observer-Pattern

Das Observer-Pattern ist eine Weiterentwicklung des Command-Patterns. Angenommen, Sie haben ein Objekt der Klasse SmokeDetector, die im Falle eines Brandes alle anderen Objekte, die es interessiert, über den Brand benachrichtigt. Dazu benötigen wir wieder ein Interface. Beim Observer-Pattern verwendet man meistens die Endung ...listener, hier heißt die Schnittstelle also SmokeDetectorListener:

package de.jbb.observerpattern;

public interface SmokeDetectorListener{

  public void fireIgnited(String location);

}

Der Rauchmelder verwaltet alle seine Zuhörerer in einer Liste und bietet Methoden, um einen neuer Listener zu registrieren oder wieder zu löschen. Zudem hat er noch die Methode falseAlarm(), die den Melder auslöst und alle Hörer benachrichtigt:

package de.jbb.observerpattern;

public class SmokeDetector{

  private ArrayList<SmokeDetektorListener> listeners;

  public SmokeDetector(){
    listeners = new ArrayList<SmokeDetectorListener>();
  }
 
  public void falseAlarm(){
    for(SmokeDetectorListener listener: listeners){
      listener.fireIgnited("SmokeDetector #2");
    }
  }

  public void addListener(SmokeDetectorListener listener){
    listeners.add(listener);
  }

  public void removeListener(SmokeDetectorListener listener){
    listeners.remove(listener);
  }

}

Die Feuerwehr möchte nun natürlich auf den Brand reagieren und implementiert SmokeDetectorListener:

package de.jbb.observerpattern;

public class FireDepartment implements SmokeDetectorListener{

  @Override public void fireIgnited(String location){
    turnOut(location);
  }

  public void turnOut(String location){
    System.out.println("Turned out");
  }  

}

Von außen kann die Feuerwehr dem Rauchmelder nun mit addListener(new FireDepartment()) hinzugefügt werden, oder wenn die Feuerwehr selbst eine Instanz des Rauchmelders hat, auch mit addListener(this).

Der Besitzer des Hauses würde wahrscheinlich anders reagieren, er würde das Haus versuchen zu löschen. Doch dies ist dem Rauchmelder selbst egal, er hat seine Zuhörer benachrichtigt, aber es ist ihm schnuppe, was sie mit der Information anfangen.

Bekannt?

Eigentlich sollten Sie den letzten Abschnitt mit einem kleinem Déjà-Vu-Gefühl gelesen haben. Richtig! Diese Methode wird in Swing von allen Komponenten verwendet. Also WindowListener, ActionListener, MouseListener usw. Es dürfte klar sein, dass das ganze in Swing noch ausgefeilter ist, mit Event-Objekten und weiteren Vererbungen zwischen den Listenern. Aber im Grunde ist es dasselbe.
Auch haben die Listener in Swing mehr Methoden, je nach dem, was passiert ist. Bei unserem SmokeDetectorListener könnten wir bespielsweise noch eine Methode angeben, die benachrichtigt wird, sobald die Battarien des Rauchmelders leer sind. Die Feuerwehr würde für diese Methode dann nur einen leeren Rumpf bekommen, während der Eigentümer den Code zum Auswechseln der Batterien hätte.

Schreibe einen Kommentar

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.