Patters (cont’d): The Observer Pattern
Posted by ice09 on August 9, 2008
I implemented the Observer Pattern with a central registry which is synchronized and uses WeakHashMaps, thereby trying to omit memory leaks.
The Notification Manager:
package observer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class NotificationManager implements Observable {
private Map<Integer, List<Observer>> observers = new WeakHashMap<Integer, List<Observer>>();
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final Lock read = readWriteLock.readLock();
private final Lock write = readWriteLock.writeLock();
public void notifyObservers(Object source, Object arg) {
List<Observer> obs = null;
read.lock();
try {
if (!observers.containsKey(source.hashCode())) {
return;
} else {
obs = observers.get(source.hashCode());
}
for (Observer observer : obs) {
observer.update(arg);
}
} finally {
read.unlock();
}
}
public void register(Object target, Observer observer) {
List<Observer> obs = null;
write.lock();
try {
if (!observers.containsKey(target.hashCode())) {
obs = new ArrayList<Observer>();
} else {
obs = observers.get(target.hashCode());
}
if (!obs.contains(observer)) {
obs.add(observer);
}
observers.put(target.hashCode(), obs);
} finally {
write.unlock();
}
}
public void remove(Object target, Observer observer) {
List<Observer> obs = null;
write.lock();
try {
if (!observers.containsKey(target.hashCode())) {
return;
} else {
obs = observers.get(target.hashCode());
}
if (!obs.contains(observer)) {
return;
}
obs.remove(observer);
} finally {
write.unlock();
}
}
public String toString() {
return "#Observers: ".concat(String.valueOf(observers.entrySet().size()));
}
}
The Observer interface:
package observer;
public interface Observer {
public void update(Object arg);
}
The Observable interface implemented by the NotificationManager:
package observer;
public interface Observable {
public void register(Object target, Observer observer);
public void remove(Object target, Observer observer);
public void notifyObservers(Object source, Object arg);
}
The unit test class extending AbstractDependencyInjectionSpringContextTests:
package observer;
import org.springframework.test.AbstractDependencyInjectionSpringContextTests;
public class ObserverTest extends AbstractDependencyInjectionSpringContextTests implements Observer {
protected NotificationManager notificationmanager;
public ObserverTest() {
setPopulateProtectedVariables(true);
}
public ObserverTest(String s) {
super(s);
setPopulateProtectedVariables(true);
}
public void testNM() {
ObservableObject oo = new ObservableObject();
notificationmanager.register(oo, this);
oo.notifyObs();
notificationmanager.remove(oo, this);
oo.notifyObs();
}
@Override
protected String[] getConfigLocations() {
return new String[]{"applicationContext-test.xml"};
}
public void update(Object arg) {
System.out.println("called with " + arg);
}
private class ObservableObject {
public void notifyObs() {
notificationmanager.notifyObservers(this, "bla");
}
}
}
The config, the notificationmanager is injected into the protected variable in the junit test:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="notificationmanager" class="observer.NotificationManager"/> </beans>