Einführung Inhaltsverzeichnis 2. Darstellung von Variablen und Werten

9. Generische Observer

Die Unterscheidung der Ereignisse aufgrund des Typs (getClass()) den übergebenen Parameter ist veraltet. Mit Hilfe einer eigenen generischen Observer-Implementierung kann die Aufgabe eleganter gelöst werden. Diese erhält als Typparameter ein enum mit der Liste aller auftretenden Ereignisse:

public interface Observer<Event extends Enum<Event>> { // Observer.java

   void update(Observable<Event> o, Event e); }

public abstract class Observable<Event extends Enum<Event>> { // Observable.java

   private List<Observer<Event>> observerList = new ArrayList<Observer<Event>>();

   void addObserver(Observer<Event> o) {

     observerList.add(o);  }

   void notifyObservers() {

     for (Observer<Event> o : observerList)

        o.update(this, Event event); }

  

Außer diesen Observable-Methoden, die wir für die Uhr brauchen werden, können die weiteren aus java.util.Observable ähnlich implementiert werden.

Um den generischen Observer für die Uhr zu benutzen, brauchen wir ein enum aller auftretenden Ereignisse:

enum Ereignis { EIN, AUS, UM, TICK, INIT } // Ereignis.java

Die Schnittstelle IModel erweitert jetzt Observer<Ereignis>:

public interface IModel extends Observer<Ereignis> { // IModel.java

   static class Uhrzeit { … } // wie oben

   Uhrzeit getUhrzeit();

   long getFrequenz(); }

Bei der Ereignisbehandlung in der Model-Implementierung, in der von Observer<> geforderte update() entfällt die gefährliche Konvertierung beim Ermitteln des Ereignisses, weil Observable jetzt generisch ist:

public void update(Observable<Ereignis> o, Ereignis e) { // Model.java

   switch (e) {

     case EIN: uhrAn = true; break;

     case AUS: uhrAn = false; break;

     case UM: frequenz = frequenz == FREQUENZ ? FREQUENZ_UM : FREQUENZ; break;

     case INIT: case TICK: tick(); } }

Control ist jetzt eine Unterklasse von Observable<>:

public class Control extends Observable<Ereignis> { // Control.java

   public Control(final IModel model) {

     new Thread() … } // wie oben, mit notifyObservers(Ereignis.TICK);

   public void start() {

     notifyObservers(Ereignis.INIT); } }

In diesem Konstruktor findet nur die Initialisierung des Ticken-Threads statt. In der getrennten start()-Methode (die im main nach dem Hinzufügen der Observer wie Model und die Views aufgerufen wird) werden alle Observer benachrichtigt (notifyObservers(Ereignis.INIT)), dass sie ihre Initialisierung durchführen. Bei Model ist dies nur ein tick(); die Views sollen ihren Oberflächenelementen Lauscher hinzufügen.

DigitalView verarbeitet diese Ereignisse in der update()-Methode. Die Knöpfe können wir jetzt in einem Map den Ereignissen zuordnen und für ihre Beschriftung die Ereignisnamen verwenden. Dies hat den Vorteil, dass später neue Knöpfe (mit neuen Ereignissen) einfach hinzugefügt werden können:

public class DigitalView extends JFrame implements Observer<Ereignis> { // DigitalView.java

   … // Konstanten und Attribute wie früher

   private Map<Ereignis, JButton> knöpfe = new EnumMap<Ereignis, JButton>(Ereignis.class);

   public DigitalView(IModel model) {

     … // Initialisierung der Oberfläche wie früher

     for (final Ereignis ereignis : Ereignis.values())

        if (ereignis.ordinal() >= Ereignis.EIN.ordinal() &&

              ereignis.ordinal() <= Ereignis.UM.ordinal()) {

           // nur für die Knopf-Steuerung-Ereignisse (zwischen EIN und UM)

           JButton knopf = new JButton(ereignis.name());

           knöpfe.put(ereignis, knopf);

           super.add(knopf); }

     knöpfe.get(Ereignis.EIN).setEnabled(false);

      pack(); }

   @Override // Observer

   public void update(final Observable<Ereignis> o, final Ereignis e) {

     switch (e) {

        case EIN: knöpfe.get(Ereignis.EIN).setEnabled(false);            

           knöpfe.get(Ereignis.AUS).setEnabled(true); break;

        case AUS: knöpfe.get(Ereignis.EIN).setEnabled(true);

           knöpfe.get(Ereignis.AUS).setEnabled(false); break;

        case TICK: Uhrzeit uhrzeit = model.getUhrzeit();

           anzeige.setText(String.format("%02d:%02d:%02d", uhrzeit.getStunde(),

              uhrzeit.getMinute(), uhrzeit.getSekunde())); break;

        case INIT:

           for (final Ereignis ereignis : Ereignis.values()) {

              JButton knopf = knöpfe.get(ereignis);

              if (knopf != null) // nur für die Knopf-Ereignisse

                knopf.addActionListener(new ActionListener() {

                   @Override

                   public void actionPerformed(ActionEvent e) {

                      o.notifyObservers(ereignis); } }); } } } }

In der letzten Programmzeile ist erkennbar, dass jede Knopf-Ereignisbehandlungsmethode (actionPerformed) nur aus dem Aufruf von notifyObservers() besteht.

In KreisView.update() müssen wir die Tastenschläge einzeln den Ereignissen zuordnen:

@Override // Observer // KreisView.java

public void update(final Observable<Ereignis> o, Ereignis e) {

   if (e != Ereignis.INIT) // alle Oberflächenereignisse

     repaint();

   else // INIT

     super.addKeyListener(new KeyAdapter() {

        @Override

        public void keyPressed(KeyEvent e) {

           switch (e.getKeyChar()) {

           case 'E': o.notifyObservers(Ereignis.EIN); break;

           case 'A': o.notifyObservers(Ereignis.AUS); break;

           case 'U': o.notifyObservers(Ereignis.UM); } } }); }

Auf diese Weise kann nun das Entwurfsmuster Observer für die Nachrichtenverteilung innerhalb von MVC eingesetzt werden.

Einführung Inhaltsverzeichnis 2. Darstellung von Variablen und Werten


Version: 19. April 2012

© Prof. Solymosi, 20102 Beuth-Hochschule für Technik Berlin, Fachbereich VI (Informatik und Medien)

solymosibht-berlin.de