Understanding Observer Designing Pattern by Example

 

“The Observer pattern define a one to many dependency between objects so that when one object changes state, all of its dependents are notified and updated automatically.”

With the observer pattern, the source is the object that contain the state and control it. The Observer on the other hand use the state, even if they don’t own it. The observer pattern provides an object design where the subject and observer are loosely coupled.

Here is the Class Diagram explaining

2010-04-11_1824

Benefit of loosely coupling

  • The only thing the subject knows about an Observer is that it implements a certain interface
  • We can add new Observer at any time
  • We never need to modify subject to add more observer.
  • We can use subject or observer independently of each other.
  • Change in either observer or Source will not affect the other.

Here is an example :

This example explains Observer pattern, here source generates random characters and observer, Java Swing component listen this event and display on windows. There is another observer which listen to keyboard hits and matches entered character with auto generated character.

2010-04-11_1824_001

Source Interface – CharacterSource

Source Concrete Implementation – RandomCharacterGenerator

Observer – CharacterDisplayCanvas

The Code

CharacterSource.java

  1: 
  2: package demo.thread.source;
  3: import demo.thread.listener.CharacterListener;
  4: 
  5: public interface CharacterSource {
  6:     public void addCharacterListener(CharacterListener cl);
  7:     public void removeCharacterListener(CharacterListener cl);
  8:     public void nextCharacter( );
  9: }


RandomeCharacterGenerator.java



  1: package demo.thread.source;
  2: 
  3: 
  4: import demo.thread.event.CharacterEventHandler;
  5: import demo.thread.listener.CharacterListener;
  6: import java.util.*;
  7: 
  8: public class RandomCharacterGenerator extends Thread implements CharacterSource {
  9: 
 10:     static char[] chars;
 11:     static String charArray = "abcdefghijklmnopqrstuvwxyz0123456789";
 12:     private boolean done;
 13: 
 14:     static {
 15:         chars = charArray.toCharArray();
 16:     }
 17:     Random random;
 18:     CharacterEventHandler handler;
 19: 
 20:     public RandomCharacterGenerator() {
 21:         random = new Random();
 22:         handler = new CharacterEventHandler();
 23:         done = false;
 24:     }
 25: 
 26:     public void setDone() {
 27:         done = true;
 28:     }
 29: 
 30:     public int getPauseTime() {
 31:         return (int) (Math.max(6000, 5000 * random.nextDouble()));
 32:     }
 33: 
 34:     public void addCharacterListener(CharacterListener cl) {
 35:         handler.addCharacterListener(cl);
 36:     }
 37: 
 38:     public void removeCharacterListener(CharacterListener cl) {
 39:         handler.removeCharacterListener(cl);
 40:     }
 41: 
 42:     public void nextCharacter() {
 43:         handler.fireNewCharacter(this,
 44:                 (int) chars[random.nextInt(chars.length)]);
 45:     }
 46: 
 47:     public void run() {
 48:         while (!done) {
 49:            nextCharacter();
 50:             try {
 51:                 Thread.sleep(getPauseTime());
 52:             } catch (InterruptedException ie) {
 53:                return;
 54:            }
 55:         }
 56:     }
 57: }
 58: 
 59:         }
 60:     }
 61: }

CharacterListener.java

 

  1: package demo.thread.listener;
  2: 
  3: import demo.thread.event.CharacterEvent;
  4: 
  5: public interface CharacterListener {
  6:     public void newCharacter(CharacterEvent ce);
  7: }

 

CharacterEvent.java

 

  1: package demo.thread.event;
  2: import demo.thread.source.CharacterSource;
  3: 
  4: public class CharacterEvent {
  5: 
  6:     public CharacterSource source;
  7:     public int character;
  8: 
  9:     public CharacterEvent(CharacterSource cs, int c) {
 10:         source = cs;
 11:         character = c;
 12:     }
 13: }

 

CharacterEventHandler.java

 

 

  1: package demo.thread.event;
  2: import demo.thread.listener.CharacterListener;
  3: import demo.thread.source.CharacterSource;
  4: import java.util.*;
  5: 
  6: public class CharacterEventHandler {
  7:     private Vector listeners = new Vector();
  8:     public void addCharacterListener(CharacterListener cl) {
  9:         listeners.add(cl);
 10:     }
 11: 
 12:     public void removeCharacterListener(CharacterListener cl) {
 13:         listeners.remove(cl);
 14:     }
 15: 
 16:     public void fireNewCharacter(CharacterSource source, int c) {
 17:        CharacterEvent ce = new CharacterEvent(source, c);
 18:       CharacterListener[] cl = (CharacterListener[]) listeners.toArray(new CharacterListener[0]);
 19:         for (int i = 0; i < cl.length; i++) {
 20:             cl[i].newCharacter(ce);
 21:         }
 22:     }
 23: }
 24: 

 

 

CharacterDisplayCanvas.java

 

  1: package demo.thread.ui;
  2: import demo.thread.event.CharacterEvent;
  3: import demo.thread.listener.CharacterListener;
  4: import demo.thread.source.CharacterSource;
  5: import java.awt.*;
  6: 
  7: import javax.swing.*;
  8: 
  9: public class CharacterDisplayCanvas extends JComponent implements CharacterListener {
 10:     protected FontMetrics fm;
 11:     protected char[] tmpChar = new char[1];
 12:     protected int fontHeight;
 13:     
 14:     public CharacterDisplayCanvas() {
 15:         setFont(new Font("Monospaced", Font.BOLD, 18));
 16:         fm = Toolkit.getDefaultToolkit().getFontMetrics(getFont());
 17:         fontHeight = fm.getHeight();
 18:     }
 19: 
 20:     public CharacterDisplayCanvas(CharacterSource cs) {
 21:         this();
 22:         setCharacterSource(cs);
 23:     }
 24: 
 25:     public void setCharacterSource(CharacterSource cs) {
 26:         cs.addCharacterListener(this);
 27:     }
 28: 
 29:     public Dimension preferredSize() {
 30:         return new Dimension(fm.getMaxAscent() + 10,
 31:                 fm.getMaxAdvance() + 10);
 32:     }
 33: 
 34:     public synchronized void newCharacter(CharacterEvent ce) {
 35:         tmpChar[0] = (char) ce.character;
 36:        repaint();
 37:     }
 38: 
 39:     @Override
 40:     protected synchronized void paintComponent(Graphics gc) {
 41:         Dimension d = getSize();
 42:         gc.clearRect(0, 0, d.width, d.height);
 43:         if (tmpChar[0] == 0) {
 44:             return;
 45:         }
 46:         int charWidth = fm.charWidth((int) tmpChar[0]);
 47:         gc.drawChars(tmpChar, 0, 1,
 48:                 (d.width - charWidth) / 2, fontHeight);
 49:     }
 50: }
 51: 


SwingTypeTester.java





  1: package demo.thread.ui;
  2: 
  3: import demo.thread.event.CharacterEventHandler;
  4: import demo.thread.listener.CharacterListener;
  5: import demo.thread.source.CharacterSource;
  6: import demo.thread.source.RandomCharacterGenerator;
  7: import java.awt.*;
  8: 
  9: import java.awt.event.*;
 10: 
 11: import javax.swing.*;
 12: 
 13: public class SwingTypeTester extends JFrame implements CharacterSource {
 14:     protected RandomCharacterGenerator producer;
 15:     private CharacterDisplayCanvas displayCanvas;
 16:     private CharacterDisplayCanvas feedbackCanvas;
 17:     private JButton quitButton;
 18:     private JButton startButton;
 19:     private JButton stopButton;
 20:     private CharacterEventHandler handler;
 21:     private ScoreLabel scoreLabel;
 22: 
 23:     public SwingTypeTester() {
 24:         initComponents();
 25:     }
 26: 
 27:     private void initComponents() {
 28:         handler = new CharacterEventHandler();
 29:         displayCanvas = new CharacterDisplayCanvas();
 30:         //displayCanvas = new AnimatedCharDisplayCanvas();
 31:         scoreLabel = new ScoreLabel();
 32:         scoreLabel.setTypist(this);
 33:         feedbackCanvas = new CharacterDisplayCanvas(this);
 34:         //feedbackCanvas =
 35:         quitButton = new JButton();
 36:         startButton = new JButton();
 37:         stopButton = new JButton();
 38:         add(displayCanvas, BorderLayout.NORTH);
 39:         add(scoreLabel, BorderLayout.EAST);
 40:         add(feedbackCanvas, BorderLayout.CENTER);
 41:         JPanel p = new JPanel();
 42:         startButton.setLabel("Start");
 43:         stopButton.setLabel("Stop");
 44:         quitButton.setLabel("Quit");
 45:         p.add(startButton);
 46:         p.add(stopButton);
 47:         p.add(quitButton);
 48:         add(p, BorderLayout.SOUTH);
 49:         addWindowListener(new WindowAdapter() {
 50:             public void windowClosing(WindowEvent evt) {
 51:                 quit();
 52:             }
 53:         });
 54: 
 55:         feedbackCanvas.addKeyListener(new KeyAdapter() {
 56:             public void keyPressed(KeyEvent ke) {
 57:                 char c = ke.getKeyChar();
 58:                 if (c != KeyEvent.CHAR_UNDEFINED) {
 59:                     newCharacter((int) c);
 60:                 }
 61:             }
 62:         });
 63: 
 64:         startButton.addActionListener(new ActionListener() {
 65:             public void actionPerformed(ActionEvent evt) {
 66:                producer = new RandomCharacterGenerator();
 67:                 scoreLabel.setGenerator(producer);
 68:                 displayCanvas.setCharacterSource(producer);
 69:                 producer.setDaemon(false);
 70:                 producer.start();
 71:                startButton.setEnabled(false);
 72:                 stopButton.setEnabled(true);
 73:                 feedbackCanvas.setEnabled(true);
 74:                 feedbackCanvas.requestFocus();
 75:             }
 76:         });
 77: 
 78:         quitButton.addActionListener(new ActionListener() {
 79:             public void actionPerformed(ActionEvent evt) {
 80:                 quit();
 81:             }
 82:         });
 83: 
 84:         stopButton.addActionListener(new ActionListener() {
 85:             public void actionPerformed(ActionEvent ae) {
 86:                 producer.setDone();
 87:                 startButton.setEnabled(true);
 88:                 stopButton.setEnabled(false);
 89:                 feedbackCanvas.setEnabled(false);
 90: 
 91:             }
 92:         });
 93:         pack();
 94:     }
 95: 
 96:     private void quit() {
 97:         System.exit(0);
 98:     }
 99: 
100:     public void addCharacterListener(CharacterListener cl) {
101:         handler.addCharacterListener(cl);
102:     }
103: 
104:     public void removeCharacterListener(CharacterListener cl) {
105:         handler.removeCharacterListener(cl);
106:     }
107: 
108:     public void newCharacter(int c) {
109:         handler.fireNewCharacter(this, c);
110:     }
111: 
112:     public void nextCharacter() {
113:         throw new IllegalStateException("We don't produce on demand");
114:     }
115: 
116:     public static void main(String args[]) {
117:         new SwingTypeTester().show();
118:     }
119: }
120: 


 



The Output :



2010-04-11_1921



2010-04-11_1921_001

Comments

Popular posts from this blog

State Design Pattern by Example

Eclipse command framework core expression: Property tester

Composite Design Pattern by example