http://www.developer.com/

Back to article

Working with Design Patterns: Observer


August 17, 2007

In an earlier article on threading, I provided an implementation for a fax server (see Listing 1 for code implemented in the core classes). Within the server code, I directly embedded System.out.println statements. Although it's an acceptable technique for demonstration purposes, I rarely want to intermingle my server code and display output.

Listing 1: Fax server and client.

// Fax.java:
package waitperiod;

import java.util.concurrent.*;

public class FaxServer {
   private DelayQueue<FaxTransmission> queue =
      new DelayQueue<FaxTransmission>();
   private Dialer dialer;
   private Transmitter transmitter;

   public FaxServer(Dialer dialer, Transmitter transmitter) {
      this.dialer = dialer;
      this.transmitter = transmitter;
   }

   public void start() {
      new Thread(new Runnable() {
         public void run() {
            while (true) {
               try {
                  transmit(queue.take());
               }
               catch (InterruptedException e) {
               }
            }
         }
      }).start();
   }

   private void transmit(FaxTransmission transmission) {
      if (dialer.connect(transmission.getFax().to())) {
         System.out.printf("sending %s.", transmission);
         transmitter.send(transmission.getFax());
         System.out.println("completed");
      }
      else {
         System.out.printf("busy, queuing %s for resend%n",
                           transmission);
         transmission.setToResend();
         queue.add(transmission);
      }
   }

   public void send(Fax fax) {
      System.out.printf("queuing %s%n", fax);
      queue.add(new FaxTransmission(fax));
   }
}

// Client.java:
import java.util.*;
import util.*;

public class Client {
   public static void main(String[] args) {
      Fax fax1 = new Fax("1", "5555", "some message 1");
      Fax fax2 = new Fax("2", "6666", "some message 2");
      Fax fax3 = new Fax("3", "7777", "some message 3");
      Fax fax4 = new Fax("4", "8888", "some message 4");

      Dialer dialer = new Dialer() {
         private Random random = new Random();
         public boolean connect(String number) {
            return random.nextInt(5) == 0;
         }
      };

      Transmitter transmitter = new Transmitter()  {
         public void send(Fax fax) {
            for (int i = 0; i < 10; i++) {
               System.out.print(".");
               ThreadUtil.pause(1);
            }
         }
      };

      FaxServer server = new FaxServer(dialer, transmitter);
      server.start();
      server.send(fax1);
      ThreadUtil.pause(1);
      server.send(fax2);
      ThreadUtil.pause(1);
      server.send(fax3);
      ThreadUtil.pause(1);
      server.send(fax4);
   }
}

Separating user interface output from logic is almost always a good idea. It sets the stage for cheaper and safer maintenance in the future, when, for example, I want to change the fax server from a console application to something that interacts with a nice LCD output panel. It's also easier to write tests against single-purpose code. In contrast, having code that combines the two purposes of presentation and fax management is considerably more difficult to test.

Generally, I want the clients exercising my fax server to be the ones driving where the output should go and how it should be formatted. A simple solution would be to have the client object pass a reference to itself to the FaxServer. The FaxServer would call back to the client when anything of interest occurred.

But, such a simple solution is less than ideal. The client is already cognizant of the FaxServer, which is necessary. Now, the FaxServer would need to know the type of the client. Changes to the client could potentially ripple into the FaxServer. This sort of two-way dependency is, in most cases, a bad design choice.

The observer design pattern provides a simple solution: Instead of passing a client reference to the fax server, the client passes an abstraction of itself. It defines itself as an "observer" by implementing an interface (java.util.Observer), and then passes the interface reference to the fax server. The fax server knows only that it's interacting with an Observer instance, not a ConsoleClient or a FancyLcdPanelClient.

Use of observer is so common that Java provides a built-in implementation of the pattern. Java supplies the java.util.Observer interface, which defines the single method update (see Listing 2). It also supplies the java.util.Observable class. A class defines itself as the target of observation by extending Observable.

Listing 2: java.util.Observer.

public interface Observer {
    void update(Observable o, Object arg);
}

Clients wanting to observe something can call the addObserver method against an Observable subclass instance. Making this call effectively registers the interested client with the Observable object. The argument to addObserver must be of the interface type Observer. The most succinct way of accomplishing things is to use an anonymous inner class. Or, the client object itself could implement the Observer interface.

Here, my client code implements the update method as part of an anonymous inner class:

FaxServer server = new FaxServer(dialer, transmitter);
server.addObserver(new Observer() {
   public void update(Observable o, Object arg) {
      FaxServer server = (FaxServer)o;
      System.out.println("Status: " + server.status());
   }});

Calls to the update method come along with two arguments: a reference to the Observable object, plus an optional Object instance that can be used for passing pertinent information. In my implementation, I cast the Observable reference to a FaxServer reference, and then ask for its status string.

Listing 3 provides the modified FaxServer code. My changes are shown in bold. Instead of System.out statements, I call a setStatus method. This method changes a status instance variable to contain text representing the new state. The setStatus method then calls setChanged, an Observable method that indicates a relevant change was made. Without this call, no notifications can go out. Finally, code in the setStatus method calls the Observable method notifyObservers, which ultimately triggers the call of update methods defined on registered Observer objects.

Listing 3: The modified FaxServer class.

import java.util.*;
import java.util.concurrent.*;

public class FaxServer extends Observable {
   private DelayQueue<FaxTransmission> queue =
      new DelayQueue<FaxTransmission>();
   private Dialer dialer;
   private Transmitter transmitter;
   private String status;

   public FaxServer(Dialer dialer, Transmitter transmitter) {
      this.dialer = dialer;
      this.transmitter = transmitter;
   }

   public void start() {
      new Thread(new Runnable() {
         public void run() {
            while (true) {
               try {
                  transmit(queue.take());
               }
               catch (InterruptedException e) {
               }
            }
         }
      }).start();
   }

   private void transmit(FaxTransmission transmission) {
      if (dialer.connect(transmission.getFax().to())) {
         setStatus(String.format("sending %s.", transmission));
         transmitter.send(transmission.getFax());
         setStatus("completed");
         notifyObservers(this);
      }
      else {
         setStatus(
            String.format("busy, queuing %s for resend%n",
                          transmission));
         transmission.setToResend();
         queue.add(transmission);
      }
   }

   public void send(Fax fax) {
      setStatus(String.format("queuing %s%n", fax));
      queue.add(new FaxTransmission(fax));
   }

   public String status() {
      return status;
   }

   private void setStatus(String status) {
      this.status = status;
      setChanged();
      notifyObservers();
   }
}

The addObserver defined on the Observable method may be called by multiple interested clients. Observable also provides a deleteObserver method. A note of caution: Because Observable objects can hold references to many interested clients, it's possible for me to introduce memory leaks if I'm not careful about relinquishing observation.

My implementation of the fax server is probably still not ideal. Embedded status strings aren't a good idea if I want to ship my server to international destinations. I'd probably want to notify the client of a status enum, or maybe just a message ID.

Also, nothing prevents me from implementing my own implementation of the observer pattern. It's nice to have built-in functionality, but somehow it makes more sense to just roll my own. For the fax server, for example, I might have provided an interface named FaxObserver, defining within it various status callbacks:

interface FaxObserver {
   void sendInitiated();
   void sendCompleted();
   void queueing();
   void resending();
}

Introducing my own custom callback interface can significantly improve readability when I have more complex callback needs.



Click here for a larger image.

observer.jpg Implementation of the observer pattern.

Reference

[Gamma] Gamma, E., et. al. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley Professional, 1995.

About the Author

Jeff Langr is a veteran software developer celebrating his 25th year of professional software development. He's authored two books and dozens of published articles on software development, including Agile Java: Crafting Code With Test-Driven Development (Prentice Hall) in 2005. You can find out more about Jeff at his site, http://langrsoft.com, or you can contact him via email at jeff at langrsoft.com.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date