Dienstag, 31. August 2010

Know the API and do it right

Wesentlich für die Programmierung von Java-Anwendungen sind gute Java API Kenntnisse. Die Java API beinhaltet reichhaltige Funktionalitäten zur Verarbeitung und Speicherung von Daten. Neben den viel diskutierten Java Collections sind in der Java API bereits Musterimplementierungen vorhanden.

Das Wissen über diese Muster und deren Anwendung führt zu robusten und wartungsfreundlichen Applikationen. Bevor man selbst ein Muster programmiert, ist es deshalb ratsam, die Java API zu konsultieren, um zu evaluieren, ob nicht schon die gewünschte Musterimplementierung in der API vorhanden ist.

In diesem Blogbeitrag wird anhand des Observer-Musters eine Eigenimplementierung und die Java API Version des Musters vorgestellt. Das Observer-Muster definiert eine 1-n Beziehung zwischen Objektinstanzen. Das Observer-Muster setzt sich aus einem Subjekt und den Beobachtern zusammen. Das Subjekt ist das Objekt, dass seine Zustandsänderungen den registrierten Beobachtern mitteilt. Für die Implementierung des Musters werden die folgenden zwei Schnittstellen und deren Implementierungen benötigt.

Subjekt - Schnittstelle

public interface Subject {

    public void registerObserver(Observer observer);
    public void registerObservers(Observer ... observers);
    public void removeObserver(Observer observer);
    public void removeObservers();
    public void notifyObservers(Object state);  
}

Beobachter - Schnittstelle

public interface Observer {

    public void update(Object state);
    public Object getState();
}

Implementierung des Subjektes:

import java.util.ArrayList;
import java.util.List;

public class SubjectBean implements Subject {

    private List<Observer> observers = new ArrayList<Observer>();  
  
    @Override
    public void notifyObservers(Object state) {
     
        for(Observer observer : observers) {
            observer.update(state);
        }      
    }

    @Override
    public void removeObservers() {
     
        observers.clear();
    }
  
    @Override
    public void removeObserver(Observer observer) {
     
        final int index = observers.indexOf(observer);
        if(index >= 0) {
            observers.remove(index);
        }      
    }

    @Override
    public void registerObserver(Observer observer) {
     
        observers.add(observer);      
    }
  
    @Override
    public void registerObservers(Observer ... observerEllipse) {
     
        for(Observer observer: observerEllipse) {
            observers.add(observer);
        }
    }      
}

Implementierung des Beobachters:

public class ObserverBean implements Observer {
  
    private Object state;
  
    @Override
    public void update(Object state) {
      
        this.state = state;
    }
  
    @Override
    public Object getState() {
      
        return state;
    }
}

Unit-Tests des Musters:

import static org.junit.Assert.assertEquals;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class ObserverTest {

    private static final String SUBJECT_STATE_MESSAGE = "new-state";
    private static final int COUNT_OBSERVERS = 2;
 
    private Subject subject;
    private Observer[] observers;
    
    @Before
    public void setUp() {
    
        subject = new SubjectBean();
        observers = createObservers(COUNT_OBSERVERS);
        subject.registerObservers(observers);
    }

    @After
    public void tearDown() {
    
        subject.removeObservers();
    }
 
    @Test
    public void testBroadcast() {
           
        subject.notifyObservers(SUBJECT_STATE_MESSAGE);
    
        assertObserverStates();
    }
 
    private void assertObserverStates() {
      
        for(int i = 0; i < observers.length; i++) {        
        
            assertEquals(SUBJECT_STATE_MESSAGE,
                    ((ObserverBean)observers[i]).getState());
        }
    }
 
    private Observer[] createObservers(int countObserver) {
    
        final Observer[] observerArray = new Observer[countObserver];
        for(int i = 0; i < observerArray.length; i++) {
        
            observerArray[i] = new ObserverBean();
        }
    
        return observerArray;
    }    
}

Die Eigenentwicklung des Observer-Musters ist ausprogrammiert und rudimentär getestet. Zum Vergleich nachfolgend die Java API Implementierung des Observer-Musters.

Implementierung des Subjektes:

import java.util.Observable;

public class SubjectBean extends Observable {

    public void notifyObservers(String state) {
      
        super.setChanged();
        super.notifyObservers(state);
    }
}

Implementierung  des Beobachters:

import java.util.Observable;
import java.util.Observer;

public class ObserverBean implements Observer {

    private Object state;
   
    @Override
    public void update(Observable observer, Object state) {
       
        this.state = state;       
    }       
   
    public Object getState() {
       
        return state;
    }
}

Unit-Tests des Musters:

import static org.junit.Assert.assertEquals;
import java.util.Observable;
import java.util.Observer;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class ObserverTest {

    private static final String SUBJECT_STATE_MESSAGE = "new-state";
    private static final int COUNT_OBSERVERS = 2;
   
    private Observable subject;
    private Observer[] observerArray;
   
    @Before
    public void setUp() {
       
        subject = new SubjectBean();
        createObservers(subject, COUNT_OBSERVERS);       
    }

    @After
    public void tearDown() {
       
        subject.deleteObservers();
    }
   
    @Test
    public void testBroadcast() {
       
        ((SubjectBean)subject).notifyObservers(SUBJECT_STATE_MESSAGE);
       
        assertObserverStates();
    }
   
    private void assertObserverStates() {
       
        for(int i = 0; i < observerArray.length; i++) {           
           
            assertEquals(SUBJECT_STATE_MESSAGE,
                    ((ObserverBean)observerArray[i]).getState());
        }
    }
   
    private void createObservers(Observable subject, int countObserver) {
       
        observerArray = new Observer[countObserver];
       
        for(int i = 0; i < countObserver; i++) {
   
            observerArray[i] = new ObserverBean();
            subject.addObserver(observerArray[i]);
        }       
    }
}

Vergleicht man die beiden Implementierungen, fällt sofort auf, dass bei der Eigenentwicklung sehr viel mehr Quellcode erstellt worden ist. Bei der Java API Implementierung des Observer-Musters ist sicherlich unschön, dass der Status als "Object" Typ behandelt wird. Der Gewinn an Flexibilität wird durch die mangelnde Typsicherheit aufgewogen. Bei der Eigenentwicklung des Observer-Musters ist der Typ des Statusobjektes frei wählbar.

Die Java API Implementierung des Observer-Musters ist synchronisiert und damit auch in nebenläufigen Umgebungen einsetzbar. Die Eigenentwicklung des Observer-Musters ist zunächst noch nicht synchronisiert. Dort liegt die Schwachstelle und eine mögliche Fehlerquelle der Implementierung in nebenläufigen Umgebungen. Die Eigenentwicklung mit einem Lock zu sichern ist nicht empfehlenswert. Es bietet sich vielmehr eine threadsichere Liste an. Die "ArrayList" in der Subjektimplementierung ist deshalb durch eine threadsichere Liste auszutauschen. Als threadsichere Variante einer Liste für nebenläufige Umgebungen bietet sich die CopyOnWriteArrayList an.

Auszug aus der angepassten Subjektimplementierung:

public class SubjectBean implements Subject {
    ...
  
    private List<Observer> observers = new ArrayList<Observer>();  
    private List<Observer> observers = new CopyOnWriteArrayList<Observer>();

    ...  
}

Der Blogbeitrag veranschaulicht, dass ein Programmierer mit guten Java API Kenntnissen weniger Quellcode erstellt, auf Basis von getestetem Quellcode arbeitet und deswegen in der Lage ist, robustere und wartungsfreundlichere Java-Applikationen zu implementieren.


Der Rechtshinweis des Java Blog für Clean Code Developer ist bei der Verwendung und Weiterentwicklung des Quellcodes des Blogeintrages zu beachten.