The Observer Pattern
An Introduction with Examples in Java |
Prof. David Bernstein |
Computer Science Department |
bernstdh@jmu.edu |
A Bad Design
TickReader
must have an associated
TickWriter
and TickerTape
TickReader
much less re-usable
A Better Design
TickReader
plays a more passive roleTickReader
need only have a list of
(zero or more) TickListener
objects that it will
informTickListener
interfaceimport 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 Bad Design
A Better Design
Using the Observer Pattern
/** * 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); }
/** * 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); }
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(); } } }
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(); } }
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); } }
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); } }
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); } }