I have no problem with the Observer Pattern, only its default implementations. Most language libraries provide implementations that are way too abstract and result in good programmers writing buggy code.
As an intro for those who don’t know the back story, the Observer Pattern is a software design pattern that can be used to detach an object from its uses. There are always two players, the thing being Observed (the Observable) and the thing doing the Observing (the Observer). The typical usage scenario goes like this.
- Instance of Observable gets created
- Instance of Observer is created
- Observer registers itself to the Observable for change notifications
- Observable changes
- Notification messages are sent to all objects registered as observers.
Taking a look at the observer as defined in the Java API (see below), you can see that the Observer makes few assumptions about the object emitting the updates, the idea being that it can be reused with different kinds of Observable objects.
public interface Observer {
void update(Observable o, Object arg);
}
Unfortunately, this comes at a high cost. To make use of the update notification, the Observer typically needs to cast the Observable to a more useful type and interpret the argument appropriately. If you were planning on reusing it with many kinds of Observable objects, you’d end up with an update method resembling a switch statement for each kind of Observable thing.
As a concrete, though convoluted, example, say I have a Directory class and a DirectoryWatcher that needs to correctly display the number of files in that directory. The code might look like this:
class Directory extends Observable {
public File[] getFiles() { ... }
public void createFile(File file) {
...
notifyObservers();
}
public void deleteFile(File file) {
...
notifyObservers();
}
}
class DirectoryWatcher implements Observer {
public void update(Observable o, Object arg) {
if (o instanceof Directory) {
System.out.println(((Directory)o).getFiles().length);
}
}
}
“if (o instanceof Directory) {” Yuk!
One improvement is to require that all Observers implement a custom interface and then invoke appropriate methods on them.
For example:
class Directory {
private List<DirectoryObserver> observers = new LinkedList<DirectoryObserver>();
interface DirectoryObserver{
void fileDeleted(Directory directory, File file);
void fileCreated(Directory directory, File file);
}
public void addObserver(DirectoryObserver o) {
observers.add(o);
}
public void removeObserver(DirectoryObserver o) {
observers.remove(o);
}
public File[] getFiles() { ... }
public void createFile(File file) {
...
for (Observer o : observers) {
o.fileCreated(file);
}
}
public void deleteFile(File file) {
...
for (Observer o : observers) {
o.fileDeleted(file);
}
}
}
class DirectoryWatcher implements Directory. DirectoryObserver {
public void fileCreated(Directory directory, File file) {
System.out.println(directory.getFiles().length);
}
public void fileDeleted(Directory directory, File file) {
System.out.println(directory.getFiles().length);
}
}
Now, I know that the code above has some flaws in the way it dispatches notifications (Exceptions being one) and that it is significantly longer that the more abstract code above, but this doesn’t need to be the case. By using a helper class ObserverPool (which I don’t define here, but I will eventually) this could be brought down significantly to the code below:
class Directory {
public final ObserverPool<DirectoryObserver> observers
= new ObserverPool<DirectoryObserver>(this);
interface DirectoryObserver{
void fileDeleted(Directory directory, File file);
void fileCreated(Directory directory, File file);
}
public File[] getFiles() { ... }
public void createFile(File file) {
...
observers.broadcast("fileCreated", file);
}
public void deleteFile(File file) {
...
observers.broadcast("fileDeleted", file);
}
}
class DirectoryWatcher implements Directory.Observer {
public void fileCreated(Directory directory, File file) {
System.out.println(directory.getFiles().length);
}
public void fileDeleted(Directory directory, File file) {
System.out.println(directory.getFiles().length);
}
}
The benefits of the code above are that the observer may make use of the observable object in a type-safe way and each notification is implicitly more useful since it each kind of change may have its own method.
Problems with the above approach are that the observable object needs to call broadcast with an argument of the method on the observers that needs to be invoked. This is a magnet for bugs since a typo breaks everything at runtime. I believe this can be overcome by using Java’s Dynamic Proxy feature to create a proxy object that stands in for the observers.
Using this proxy approach the code could be:
class Directory {
public final ObserverPool<DirectoryObserver> observers
= new ObserverPool<DirectoryObserver>(DirectoryObserver.class);
interface DirectoryObserver{
void fileDeleted(Directory directory, File file);
void fileCreated(Directory directory, File file);
}
public File[] getFiles() { ... }
public void createFile(File file) {
...
observers.getProxy().fileCreated(this, file);
}
public void deleteFile(File file) {
...
observers.getProxy().fileDeleted(this, file);
}
}
class DirectoryWatcher implements Directory.DirectoryObserver {
public void fileCreated(Directory directory, File file) {
System.out.println(directory.getFiles().length);
}
public void fileDeleted(Directory directory, File file) {
System.out.println(directory.getFiles().length);
}
}
Much better, if I say so myself.
To recap, the typical implementation of the Observer Pattern trades reuse for type safety. Because of this, programmers need to write terrible code to put the checks back in. It’s my opinion that writing more specific code that doesn’t require the loss of type safety is always a better approach, and that by making use of generics and some Dynamic Proxies your end product will be more robust and be easier to understand.
Ideas, Programming Java, observer, pattern