|
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 interface
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 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);
}
}