I’m using the same contrived example of the NewsServer and NewsClient; the server controls the headlines sending one to an observer when asked. In this case the observer, or NewsClient, prints the headline to the screen. Although I’ve said that the Observable class is flawed, I’d still like to leverage its functionality, after all the code has been written, tested and it works; therefore using it entails less risk than writing our own version of Observable. The major flaw in the JDK’s Observable class is the fact that it has been designed for extension rather than composition. In order to fix this we can write a simple helper class that will run the observable functionality.
public class ObservableHelper extends Observable {
public void clearChanged() {
super.clearChanged();
}
public void setChanged() {
super.setChanged();
}
}
Now, we still want to implement the Observer Pattern as specified by the GOF, so we need to define a subject interface for our concrete observable or subject. This interface, when implemented, manages the observers and we do that by delegating to the ObservableHelper:
public interface Subject<T> {
public void addObserver(Observer o);
public void deleteObserver(Observer o);
public void deleteObservers();
public void notifyObservers();
public void notifyObservers(T arg);
}
Note that we can improve upon the JDK implementation by using generics to define this interface. This brings in stronger compile time checking making our program more robust.
The next thing to consider is the NewsServer implementation. This realises the Subject interface and manages our news headlines. Previous example NewsServers broke the single responsibility principal as they were responsible for managing headlines, running the timer thread and being the observable class. We’ve already partly fixed (or intend to fix) this with our ObservableHelper class, but we can do better by moving the threading to the NewsApp class which currently doesn’t do anything much and is really a lazy class (See ‘Lazy Class’ anti-pattern).
public class NewsServer implements Subject<String> {
private static Random rnd = new Random();
private static final String[] dummyHeadlines = { "War time bomber found on the Moon",
"There is no more news at the moment",
"Monster Raving Looney Party in landslide election win",
"Spam irradicated from the Internet", "Life found on Mars",
"Gramatical errors found in news headlines",
"Software project released on time and under budget." };
private ObservableHelper observable = new ObservableHelper();
private String chooseHeadline() {
// Next choose the headline id
int headlineId = rnd.nextInt(dummyHeadlines.length);
return dummyHeadlines[headlineId];
}
@Override
public void deleteObserver(Observer o) {
observable.deleteObserver(o);
}
@Override
public void deleteObservers() {
observable.deleteObservers();
}
@Override
public void notifyObservers() {
String headline = chooseHeadline();
notifyObservers(headline);
}
@Override
public void notifyObservers(String arg) {
observable.setChanged();
observable.notifyObservers(arg);
}
/**
* @see Observer.aggregate.Subject#addObserver(java.util.Observer)
*/
@Override
public void addObserver(Observer o) {
observable.addObserver(o);
}
}
The code below is the NewsApp, which apart from handling the threading, contains a main(...) method that runs our demonstration.
public class NewsApp implements Runnable {
private static Random rnd = new Random();
private Subject<String> newsServer;
private boolean bRun;
public NewsApp(Subject<String> newsServer) {
this.newsServer = newsServer;
}
public void start() {
Thread t = new Thread(this);
bRun = true;
t.start();
}
public void run() {
while (bRun) {
if (makeHeadlineAvailable()) {
newsServer.notifyObservers();
}
sleep(1);
}
}
private boolean makeHeadlineAvailable() {
// give ourselves a one in three
// chance of distributing a story
return rnd.nextInt(3) == 1;
}
public void sleep(int time) {
try {
SECONDS.sleep(time);
} catch (InterruptedException e) {
/* do nothing just continue */
}
}
public void stop() {
bRun = false;
}
/**
* Note that the instances of observable/observer are created using interfaces. This means
* that we can combine the Observer pattern with the factory pattern (or use Spring) to
* hide the construction of the observers as the client code doesn't really need to know
* which observers it's connected to.
*/
public static void main(String[] args) {
// This is the Observable object.
Subject<String> newsServer = new NewsServer();
// Create the observer
Observer observer = new NewsClient();
newsServer.addObserver(observer);
NewsApp app = new NewsApp(newsServer);
app.start();
System.out.println("App Started");
// Do some work
app.sleep(12);
System.out.println("Adding next Observer");
newsServer.addObserver(new NewsClient());
app.sleep(5);
System.out.println("Deleting the first Observer");
newsServer.deleteObserver(observer);
app.sleep(6);
System.out.println("Stopping the Server");
app.stop();
System.out.println("All Done...");
}
}
Finally, we need to define our observer and, again, we do this by leveraging the JDK’s Observer class:
public class NewsClient implements Observer {
/**
* This is the implementation of the Observer interface - just one method. In this case all
* we do is print the headlines to the screen. Note that the Observer class typically
* implements some view on the data, whilst the Observable class implements the data and
* its changes. Hence we may have multiple views on the data to one observable holding the
* data.
*
* @param o
* The observable object
* @param str
* The changed data - in this case cast to a string.
*/
public void update(Observable o, Object str) {
System.out.println("update: " + str.toString());
}
}
This, in our case is particularly simple as all it does is write to the screen. We could, in a real world application, implement many different observers each with different functionality.
Just in case you’re not following all this in the code, the UML diagram spells it out in pictoral detail:
Although lacking in a lot of detail, you can see that the above diagram follows the GOF Observer pattern as described in my previous blog: the ConcreteSubject is replaced by the NewsServer and the ConcreteObserver is replaced by the NewsClient.
No comments:
Post a comment