~/home of geeks

OO Observer Pattern

· 637 Wörter · 3 Minute(n) Lesedauer

patterns in nature

Das Observer-Muster ist wohl eines der einfachsten und am häufigsten benutzten Patterns überhaupt. Um so beschämender ist die Umsetzung dieses Musters in der Java API (java.util.Observable). Wenn man dieses nämlich benutzen möchte, so muss man von Observable erben, welches bei Einfachvererbung dazu führt, dass man von eigenen Klassen nicht mehr erben kann. Nun habe ich mich nicht umsonst etliche Semester mit OO-Konzepten herumgeschlagen und habe daher eine wiederverwertbare, den OO-Konzepten besser entsprechende, auf Komposition statt Vererbung basierende Umsetzung des Observer-Musters erstellt.

Das Observer-Muster besteht bekanntermaßen aus einem Objekt, das observiert (beobachtet) wird (Observable) und einem, welches die Observierung macht (Observer). Dabei muss sich der Observer beim Observable anmelden, auf dass er bei Änderungen im Observable von diesem benachrichtigt wird.

Der erste Schritt besteht daraus, die in der Java API benötigte Vererbung durch Delegation aufzuheben. Jeder Observable erhält einen Observer-Manager, welcher für die Verwaltung und Benachrichtigung aller beim Observable registrierten Observer übernimmt. Hierzu werden zwei Interfaces definiert: Eines für den Observable und eines für den Observer-Manager.

public interface IObserverManager{
  public void addObserver(IObserver o);
  public void removeObserver(IObserver o);
  public void notifyObservers(IObservable o);
}

Die Methoden sprechen für sich. notifyObservers(...) kann nun vom Observable benutzt werden, um alle Observer zu benachrichtigen. Jeder Observable muss seine eigene Observer-Manager Instanz benutzen. Das Interface für den Observable sieht entsprechend so aus:

public interface IObservable{
  public IObserverManager getObserverManager();
}

Die Registrierung eines Observers bei einem Observable wird also auf die Observer-Manager delegiert. Schließlich das Interface für die Observer:

public interface IObserver{
  public void notify(IObservable o);
}

Da die Aufgaben eines Observer-Managers stets die gleichen sind, lohnt es sich eine einfache Implementierung zu erstellen und wieder zu benutzen. Zur Aufbewahrung der Observer kann jegliche lineare Datenstruktur benutzt werden, wie z.B. alle Klassen, die das Interface java.util.List implementieren. Im folgenden benutze ich eine ArrayList.

import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
  
public class ObserverManager implements IObserverManager{
  private List mObservers = new ArrayList();
  
  public void addObserver(IObserver o){
    if (!mObservers.contains(o)) mObservers.add(o);
  }
  
  public void removeObserver(IObserver o){
    mObservers.remove(o);
  }
  
  public void notifyObservers(IObservable o){
    Iterator iter = mObservers.iterator();
    while (iter.hasNext())
       ((IObserver) iter.next()).notify(o);
  } 
}

Der Einsatz ist denkbar einfach und kommt ganz ohne Vererbung aus. Das Observable muss lediglich eine Instanz vom Observer-Manager bereitstellen und bei bedarf benutzen:

public class MyObservabe implements IObservable{
  private IObserverManager mObserverManager = new ObserverManager();
  /* ... */
  public void changeState(){
    /* ... */
    mObserverManager.notifyObservers(this);
  }
  
  public IObserverManager getObserverManger(){
    return mObserverManager;
  }
}

Die Observer müssen sich lediglich registrieren, oder registriert werden und die Methode notify(IObservable) implementieren:

public class MyObserver implements IObserver{
  private MyObservable myObservable = new MyObservable();
  
  public MyObserver(){
    myObseravble.getObserverManager().
       addObserver(this);
  }
  
  public void notify(IObservable o){
    if (myObservable==o){
      myObservable.getState();
      doSomething();
    }
  }
}

Die if-Bedingung zur Identifizierung des Observables kann natürlich nur benutzt werden, wenn der Observable bekannt ist. Wurde der Observable außen erzeugt, so muss man auf instanceof zurück greifen. Eine Verknüpfung zwischen Observer und Observable durch eine weitere Klasse, z. B. im Rahmen eines Factory- oder Builder-Patterns kann wie folgt geschehen:

public class Main{
  public static void main(String [] argv){
    IObservable myObservable = new MyObservable();
    IObserver myObserver = new MyObserver();
    myObservable.getObserverManager().addObserver(myObserver);
  }
}

Wird im Observable die notifyObservers(...)-Methode des Observer-Managers sehr häufig benutzt, kann durch eine Änderung des Interface IObserverManager der Aufruf verkürzt werden:

public interface IObserverManager{
  public void addObserver(IObserver o);
  public void removeObserver(IObserver o);
  public void notifyObservers();
}

Hier wird das Benachrichtigen der Observer etwas stärker gekapselt: In der vorherigen Version hätte ein anderer Observable durch folgenden Code die Observer eines anderen Observable aufrufen können:

public class MySecondObservable implements IObservable{
  private IObservable myOtherObservable = new MyObserable();
  /* ... */
  public void exploitOtherObservable(){
    myOtherObservable.getObserverManager().
      notifyObservers(this);
  }
}

Nun muss jedoch dem Observer-Manager bei der Erzeugung sein Observable Objekt mitgeteilt werden:

public class ObserverManager implements IObserverManager{
  private IObservable mObservable;
  
  public ObserverManager(IObservable o){
    mObservable = o;
  }
  
  /* ... */
  
  public void notifyObservers(){
    Iterator iter = mObservers.iterator();
    while (iter.hasNext())
       ((IObserver) iter.next()).notify(mObservable);
  }
  
  /* ... */
}