JMU
The Observer Pattern
An Introduction with Examples in Java


Prof. David Bernstein
James Madison University

Computer Science Department
bernstdh@jmu.edu


Motivation
A Trading System Example

A Bad Design

images/tick_architecture_bad.gif
The Problem with this Design
A Trading System Example (cont.)

A Better Design

images/tick_architecture_better.gif
Advantage of this Design
Operationalizing this Observation
The Observer Pattern
The Observer Pattern (cont.)
images/observer1.gif
Issues Related to the Choice of Collection
Other Terminology
A Complete Example
A Complete Example (cont.)
An Implementation of the Worst Possible Design
javaexamples/observer/notcohesive/SillyTextProcessor.java
import java.awt.*;
import java.io.*;
import java.util.*;
import javax.swing.*;

/**
 * An application that reads text from the console,
 * counts the number of words that start with uppercase
 * letters, and saves the text in a file.
 *
 * This version is not cohesive
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class SillyTextProcessor
{

    /**
     * The entry-point of the application
     *
     * @param args   The command line arguments
     */
    public static void main(String[] args) throws Exception
    {
       BufferedReader      in;
       int                 lines, maxLines, ucCount;
       JFrame              frame;
       JProgressBar        bar;
       PrintWriter         out;
       String              letter, letterUC, line, word;
       StringTokenizer     tokenizer;



       // Process the command line parameter
       try 
       {
          maxLines = Integer.parseInt(args[0]);
       }
       catch (NumberFormatException nfe) 
       {
          maxLines = 5;
       }

       // Initialization
       in       = new BufferedReader(new InputStreamReader(System.in));
       out      = new PrintWriter(new FileOutputStream("archive.txt"));
       bar      = new JProgressBar(0, maxLines);
       frame    = new JFrame();

       bar.setString("Lines Read");
       bar.setStringPainted(true);
       frame.getContentPane().add(bar);
       frame.setSize(300,50);
       frame.setVisible(true);
        


       // Prompt the user
       System.out.println("Enter " +maxLines +
                          " lines of text (^Z to end):\n");

       // Read from the console
       lines = 0;
       while ((line = in.readLine()) != null) 
       {
          // Process each line


          // Indicate the progress
          ++lines;
          bar.setValue(lines);


          // Count the number of words that start with
          // uppercase characters (using Java's 
          // definition of uppercase)
          tokenizer = new StringTokenizer(line);
          ucCount = 0;
          while (tokenizer.hasMoreTokens()) 
          {
             word = tokenizer.nextToken();
             letter = word.substring(0,1);
             letterUC = letter.toUpperCase();
             if (letter.equals(letterUC)) ++ucCount;
          }
          System.out.println("Start with uppercase: "+ucCount);



          // Save the text in a file
          out.println(line);


       }

       out.flush();
       out.close();
    }

}
        
A Complete Example (cont.)

A Bad Design

images/observer-notcohesive.gif
A Complete Example (cont.)

A Better Design

images/observer-tightlycoupled.gif
A Complete Example (cont.)
A Complete Example (cont.)

Using the Observer Pattern

images/observer-good.gif
A Complete Example (cont.)

Implementing the Good Design

javaexamples/observer/LineObserver.java
/**
 * An object that is notified about lines of text
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 3.0
 */
public interface LineObserver
{

    /**
     * Handle a line of text
     *
     * @param source   The LineSubject that generated the line
     */
    public void handleLine(LineSubject source);
}
        
A Complete Example (cont.)
javaexamples/observer/LineSubject.java
/**
 * An object that notifies LinerObserver objects about
 * lines of text
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 3.0
 */
public interface LineSubject
{
    /**
     * Add an observer
     *
     * @param observer   The LineObserver to add
     */
    public void addObserver(LineObserver observer);


    /**
     * Get the last line
     *
     * @return   The line
     */
    public String getLine();



    /**
     * Notify observers of a line
     */
    public void notifyObservers();


    /**
     * Remove an observer
     *
     * @param observer   The LineObserver to remove
     */
    public void removeObserver(LineObserver observer);
}
        
A Complete Example (cont.)
javaexamples/observer/LineReader.java
import java.io.*;
import java.util.*;

/**
 * Reads a line-oriented file
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 3.0
 */
public class LineReader implements LineSubject
{
    private BufferedReader            in;

    private Collection<LineObserver>  observers;
    private int                       maxLines;
    private String                    line;


    /**
     * Explicit Value Constructor
     *
     * @param is          The InputStream to read from
     * @param maxLines    The number of lines to read
     */
    public LineReader(InputStream is, int maxLines) throws IOException
    {
       this.maxLines = maxLines;

       in        = new BufferedReader(new InputStreamReader(is));
       observers = new HashSet<LineObserver>(); 
    }



    /**
     * Add an observer
     *
     * @param observer   The LineObserver to add
     */
    public void addObserver(LineObserver observer)
    {
       observers.add(observer);
    }



    /**
     * Get the last line
     *
     * @return   The line
     */
    public String getLine()
    {
       return line;
    }



    /**
     * Notify observers of a line
     */
    public void notifyObservers()
    {
       for (LineObserver o: observers)
       {
          o.handleLine(this);
       }
    }


    /**
     * Remove an observer
     *
     * @param observer   The LineObserver to remove
     */
    public void removeObserver(LineObserver observer)
    {
       observers.remove(observer);
    }



    /**
     * Start the LineReader
     */
    public void start() throws IOException
    {
       // Read from the console and alert listeners
       while ((line = in.readLine()) != null) 
       {
          notifyObservers();
       }
    }
}
        
A Complete Example (cont.)
javaexamples/observer/SillyTextProcessor.java
import java.awt.*;
import java.io.*;
import java.util.*;
import javax.swing.*;

/**
 * An application that reads text from the console,
 * counts the number of words that start with uppercase
 * letters, and saves the text in a file.
 *
 * This version uses the Observer Pattern
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 3.0
 */
public class SillyTextProcessor
{

    /**
     * The entry-point of the application
     *
     * @param args   The command line arguments
     */
    public static void main(String[] args) throws Exception
    {
       int                 maxLines;
       LineArchiver        archiver;
       LineReader          reader;
       ProgressWindow      bar;
       UCWordCounter       counter;


       // Process the command line parameter
       try 
       {
          maxLines = Integer.parseInt(args[0]);
       }
       catch (NumberFormatException nfe) 
       {
          maxLines = 5;
       }


       // Initialization
       reader   = new LineReader(System.in, maxLines);
       bar      = new ProgressWindow(maxLines);
       archiver = new LineArchiver();
       counter  = new UCWordCounter();
        
       reader.addObserver(bar);
       reader.addObserver(archiver);
       reader.addObserver(counter);

       // Prompt the user
       System.out.println("Enter " +maxLines +
                          " lines of text (^Z to end):\n");

       reader.start();
    }

}
        
A Complete Example (cont.)
javaexamples/observer/ProgressWindow.java
import java.awt.*;
import javax.swing.*;

/**
 * A frame/window that shows progress being made
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 3.0
 */
public class ProgressWindow extends    JFrame
                            implements LineObserver
{
    int                   lines;
    JProgressBar          bar;



    /**
     * Explicit Value Constructor
     *
     * @param max   The number of "operations"
     */
    public ProgressWindow(int max)
    {
       super();

       lines = 0;
       bar = new JProgressBar(0, max);
       performLayout();
       setVisible(true);
    }


    /**
     * Handle a line of text
     */
    public void handleLine(LineSubject source)
    {
       indicateProgress();
    }




    /**
     * Indicate that progress has been made
     */
    private void indicateProgress()
    {
       lines++;
       bar.setValue(lines);
    }



    /**
     * Layout this component
     */
    private void performLayout()
    {
       setSize(300,50);

       bar.setString("Lines Read");
       bar.setStringPainted(true);

       getContentPane().add(bar);
    }
}
        
A Complete Example (cont.)
javaexamples/observer/UCWordCounter.java
import java.util.*;

/**
 * Counts the number of words
 * that start with an uppercase letter
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 3.0
 */
public class UCWordCounter implements LineObserver
{
    private int      lastCount;


    /**
     * Default Constructor
     */
    public UCWordCounter()
    {
       lastCount = 0;
    }



    /**
     * Count the words that start with an uppercase letter
     *
     * @param line   The line containing the words
     * @return       The count
     */
    private int count(String line)
    {
       int                 ucCount;
       StringTokenizer     tokenizer;
       String              letter, letterUC, word;



       ucCount = 0;
       tokenizer = new StringTokenizer(line);

       while (tokenizer.hasMoreTokens()) 
       {
          word = tokenizer.nextToken();
          letter = word.substring(0,1);
          letterUC = letter.toUpperCase();
          if (letter.equals(letterUC)) ++ucCount;
       }
        
       return ucCount;
    }



    /**
     * Handle a line of text
     *
     * @param source   The LineSubject that generated the line
     */
    public void handleLine(LineSubject source)
    {
       String        line;

       line      = source.getLine();
       lastCount = count(line); 
       displayCount();
    }


    /**
     * Display the count
     *
     * Note: Output should be handled by another class.  It is
     * included here to simplify the example.
     */
    private void displayCount()
    {
       System.out.println("Start with uppercase: "+lastCount);
    }
}
        
A Complete Example (cont.)
javaexamples/observer/LineArchiver.java
import java.io.*;


/**
 * Archives one or more lines to a file
 * named "archive.txt"
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 3.0
 */
public class LineArchiver implements LineObserver
{
    PrintWriter         out;


    /**
     * Default Constructor
     */
    public LineArchiver() throws IOException
    {
       out = new PrintWriter(new FileOutputStream("archive.txt"));
    }


    /**
     * Close the archiver
     */
    public void close() throws IOException
    {
       out.flush();
       out.close();
    }


    /**
     * Handle a line of text
     */
    public void handleLine(LineSubject source)
    {
       String     line;

       try 
       {
          line = source.getLine();
          save(line);
       } 
       catch (IOException ioe) 
       {
          // Ignore
       }
    }


    /**
     * Save a line in the archive
     *
     * @param line   The line to save
     */
    private void save(String line) throws IOException
    {
       out.println(line);
    }
}