Observer
Contents
Observer Pattern
The Observer is a design pattern used in computer programming. The Observer pattern has two parts, the subject and the observer. The pattern has a one-to-many dependency between a subject object and the observer object(s). All the observer objects are updated when a subject changes state. It is used extensively in the AWT/Swing Listeners in Java.
UML Example
Structure
Subject: Knows its observers and provides an interface for attaching and detaching observers
Observer: Defines an updating interface for objects that should be notified
ConcreteSubject: Stores states of ConcreteObservers objects. Notifies other objects when a change has been made.
ConcreteObserver: Holds a reference to the ConcreteSubjects object.
Code Examples
C# .NET
// Observer pattern -- Structural example using System; using System.Collections; namespace DoFactory.GangOfFour.Observer.Structural { // MainApp test application class MainApp { static void Main() { // Configure Observer pattern ConcreteSubject s = new ConcreteSubject(); s.Attach(new ConcreteObserver(s,"X")); s.Attach(new ConcreteObserver(s,"Y")); s.Attach(new ConcreteObserver(s,"Z")); // Change subject and notify observers s.SubjectState = "ABC"; s.Notify(); // Wait for user Console.Read(); } } // "Subject" abstract class Subject { private ArrayList observers = new ArrayList(); public void Attach(Observer observer) { observers.Add(observer); } public void Detach(Observer observer) { observers.Remove(observer); } public void Notify() { foreach (Observer o in observers) { o.Update(); } } } // "ConcreteSubject" class ConcreteSubject : Subject { private string subjectState; // Property public string SubjectState { get{ return subjectState; } set{ subjectState = value; } } } // "Observer" abstract class Observer { public abstract void Update(); } // "ConcreteObserver" class ConcreteObserver : Observer { private string name; private string observerState; private ConcreteSubject subject; // Constructor public ConcreteObserver( ConcreteSubject subject, string name) { this.subject = subject; this.name = name; } public override void Update() { observerState = subject.SubjectState; Console.WriteLine("Observer {0}'s new state is {1}", name, observerState); } // Property public ConcreteSubject Subject { get { return subject; } set { subject = value; } } } }
Java
import java.util.Observable; import java.util.Observer; public class MessageBoard extends Observable { private String message; public String getMessage() { return message; } public void changeMessage(String message) { this.message = message; setChanged(); notifyObservers(message); } public static void main(String[] args) { MessageBoard board = new MessageBoard(); Student bob = new Student(); Student joe = new Student(); board.addObserver(bob); board.addObserver(joe); board.changeMessage("More Homework!"); } } class Student implements Observer { public void update(Observable o, Object arg) { System.out.println("Message board changed: " + arg); } }
Slightly more in depth Observer Pattern in Java
//[C] 2002 Sun Microsystems, Inc.--- import java.awt.BorderLayout; import java.awt.Container; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.ArrayList; import java.util.Iterator; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; public class RunObserverPattern { public static void main(String[] arguments) { System.out.println("Example for the Observer pattern"); System.out.println("This demonstration uses a central observable"); System.out.println(" object to send change notifications to several"); System.out.println(" JPanels in a GUI. Each JPanel is an Observer,"); System.out.println(" receiving notifcations when there has been some"); System.out.println(" change in the shared Task that is being edited."); System.out.println(); System.out.println("Creating the ObserverGui"); ObserverGui application = new ObserverGui(); application.createGui(); } } class Task { private String name = ""; private String notes = ""; private double timeRequired; public Task() { } public Task(String newName, String newNotes, double newTimeRequired) { name = newName; notes = newNotes; timeRequired = newTimeRequired; } public String getName() { return name; } public String getNotes() { return notes; } public double getTimeRequired() { return timeRequired; } public void setName(String newName) { name = newName; } public void setTimeRequired(double newTimeRequired) { timeRequired = newTimeRequired; } public void setNotes(String newNotes) { notes = newNotes; } public String toString() { return name + " " + notes; } } class TaskChangeObservable { private ArrayList observers = new ArrayList(); public void addTaskChangeObserver(TaskChangeObserver observer) { if (!observers.contains(observer)) { observers.add(observer); } } public void removeTaskChangeObserver(TaskChangeObserver observer) { observers.remove(observer); } public void selectTask(Task task) { Iterator elements = observers.iterator(); while (elements.hasNext()) { ((TaskChangeObserver) elements.next()).taskSelected(task); } } public void addTask(Task task) { Iterator elements = observers.iterator(); while (elements.hasNext()) { ((TaskChangeObserver) elements.next()).taskAdded(task); } } public void updateTask(Task task) { Iterator elements = observers.iterator(); while (elements.hasNext()) { ((TaskChangeObserver) elements.next()).taskChanged(task); } } } interface TaskChangeObserver { public void taskAdded(Task task); public void taskChanged(Task task); public void taskSelected(Task task); } class TaskEditorPanel extends JPanel implements ActionListener, TaskChangeObserver { private JPanel controlPanel, editPanel; private JButton add, update, exit; private JTextField taskName, taskNotes, taskTime; private TaskChangeObservable notifier; private Task editTask; public TaskEditorPanel(TaskChangeObservable newNotifier) { notifier = newNotifier; createGui(); } public void createGui() { setLayout(new BorderLayout()); editPanel = new JPanel(); editPanel.setLayout(new GridLayout(3, 2)); taskName = new JTextField(20); taskNotes = new JTextField(20); taskTime = new JTextField(20); editPanel.add(new JLabel("Task Name")); editPanel.add(taskName); editPanel.add(new JLabel("Task Notes")); editPanel.add(taskNotes); editPanel.add(new JLabel("Time Required")); editPanel.add(taskTime); controlPanel = new JPanel(); add = new JButton("Add Task"); update = new JButton("Update Task"); exit = new JButton("Exit"); controlPanel.add(add); controlPanel.add(update); controlPanel.add(exit); add.addActionListener(this); update.addActionListener(this); exit.addActionListener(this); add(controlPanel, BorderLayout.SOUTH); add(editPanel, BorderLayout.CENTER); } public void setTaskChangeObservable(TaskChangeObservable newNotifier) { notifier = newNotifier; } public void actionPerformed(ActionEvent event) { Object source = event.getSource(); if (source == add) { double timeRequired = 0.0; try { timeRequired = Double.parseDouble(taskTime.getText()); } catch (NumberFormatException exc) { } notifier.addTask(new Task(taskName.getText(), taskNotes.getText(), timeRequired)); } else if (source == update) { editTask.setName(taskName.getText()); editTask.setNotes(taskNotes.getText()); try { editTask .setTimeRequired(Double.parseDouble(taskTime.getText())); } catch (NumberFormatException exc) { } notifier.updateTask(editTask); } else if (source == exit) { System.exit(0); } } public void taskAdded(Task task) { } public void taskChanged(Task task) { } public void taskSelected(Task task) { editTask = task; taskName.setText(task.getName()); taskNotes.setText(task.getNotes()); taskTime.setText("" + task.getTimeRequired()); } } class TaskHistoryPanel extends JPanel implements TaskChangeObserver { private JTextArea displayRegion; public TaskHistoryPanel() { createGui(); } public void createGui() { setLayout(new BorderLayout()); displayRegion = new JTextArea(10, 40); displayRegion.setEditable(false); add(new JScrollPane(displayRegion)); } public void taskAdded(Task task) { displayRegion.append("Created task " + task + "\n"); } public void taskChanged(Task task) { displayRegion.append("Updated task " + task + "\n"); } public void taskSelected(Task task) { displayRegion.append("Selected task " + task + "\n"); } } class TaskSelectorPanel extends JPanel implements ActionListener, TaskChangeObserver { private JComboBox selector = new JComboBox(); private TaskChangeObservable notifier; public TaskSelectorPanel(TaskChangeObservable newNotifier) { notifier = newNotifier; createGui(); } public void createGui() { selector = new JComboBox(); selector.addActionListener(this); add(selector); } public void actionPerformed(ActionEvent evt) { notifier.selectTask((Task) selector.getSelectedItem()); } public void setTaskChangeObservable(TaskChangeObservable newNotifier) { notifier = newNotifier; } public void taskAdded(Task task) { selector.addItem(task); } public void taskChanged(Task task) { } public void taskSelected(Task task) { } } class ObserverGui { public void createGui() { JFrame mainFrame = new JFrame("Observer Pattern Example"); Container content = mainFrame.getContentPane(); content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS)); TaskChangeObservable observable = new TaskChangeObservable(); TaskSelectorPanel select = new TaskSelectorPanel(observable); TaskHistoryPanel history = new TaskHistoryPanel(); TaskEditorPanel edit = new TaskEditorPanel(observable); observable.addTaskChangeObserver(select); observable.addTaskChangeObserver(history); observable.addTaskChangeObserver(edit); observable.addTask(new Task()); content.add(select); content.add(history); content.add(edit); mainFrame.addWindowListener(new WindowCloseManager()); mainFrame.pack(); mainFrame.setVisible(true); } private class WindowCloseManager extends WindowAdapter { public void windowClosing(WindowEvent evt) { System.exit(0); } } }
Real World Example
Here is some code from the Mozilla Internet Browser V 1.7a
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: NPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Netscape Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the NPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the NPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "prmem.h" #include "xp_obs.h" #include "prclist.h" #include "prtypes.h" #define MK_OUT_OF_MEMORY -1 typedef struct Observer { struct PRCListStr mLink; XP_ObserverProc mCallback; void* mClosure; } Observer; #define NextObserver(_obsptr_) ((Observer*)(_obsptr_)->mLink.next) #define ObserverLinks(_obsptr_) (&((_obsptr_)->mLink)) struct OpaqueObserverList { Observer* mObserverList; XP_Observable mObservable; PRBool mNotificationEnabled; }; /* Creates a new XP_Observable, to which you can add observers, who are notified when XP_NotifyObservers is called. Observerer notification is enabled by default. */ NS_Error XP_NewObserverList( XP_Observable inObservable, XP_ObserverList* outObserverList ) { NS_Error result = 0; PR_ASSERT(outObserverList != NULL); *outObserverList = PR_MALLOC(sizeof(struct OpaqueObserverList)); if (*outObserverList != NULL) { (*outObserverList)->mObserverList = NULL; (*outObserverList)->mObservable = inObservable; (*outObserverList)->mNotificationEnabled = PR_TRUE; } else { result = MK_OUT_OF_MEMORY; } return result; } /* Disposes of an XP_Observable. Does nothing with its observers. */ void XP_DisposeObserverList( XP_ObserverList inObserverList ) { Observer *obs, *next = NULL; PR_ASSERT(inObserverList != NULL); for (obs = inObserverList->mObserverList; obs != NULL; ) { next = NextObserver(obs); if (next == obs) { break; } PR_REMOVE_LINK(ObserverLinks(obs)); inObserverList->mObserverList = next; PR_FREEIF(obs); obs = inObserverList->mObserverList; } PR_FREEIF(inObserverList); } /* XP_GetObserverListObservable */ XP_Observable XP_GetObserverListObservable( XP_ObserverList inObserverList) { PR_ASSERT(inObserverList != NULL); return inObserverList->mObservable; } void XP_SetObserverListObservable( XP_ObserverList inObserverList, XP_Observable inObservable ) { PR_ASSERT(inObserverList != NULL); inObserverList->mObservable = inObservable; } /* Registers a function pointer and void* closure as an "observer", which will be called whenever XP_NotifyObservers is called an observer notification is enabled. */ NS_Error XP_AddObserver( XP_ObserverList inObserverList, XP_ObserverProc inObserver, void* inClosure ) { Observer* obs; NS_Error result = 0; PR_ASSERT(inObserverList != NULL); if (inObserverList == NULL) { return -1; } obs = PR_MALLOC(sizeof (Observer)); if (obs != NULL) { obs->mCallback = inObserver; obs->mClosure = inClosure; if (inObserverList->mObserverList == NULL) { PR_INIT_CLIST(ObserverLinks(obs)); inObserverList->mObserverList = obs; } else { PR_INSERT_AFTER(ObserverLinks(obs), ObserverLinks(inObserverList->mObserverList)); } } else { result = MK_OUT_OF_MEMORY; } return result; } /* Removes a registered observer. If there are duplicate (XP_ObserverProc/void* closure) pairs registered, it is undefined which one will be removed. Returns false if the observer is not registered. */ PRBool XP_RemoveObserver( XP_ObserverList inObserverList, XP_ObserverProc inObserver, void* inClosure ) { PRBool result = PR_FALSE; if ( inObserverList->mObserverList != NULL ) { Observer *tail = (Observer*) PR_LIST_TAIL(ObserverLinks(inObserverList->mObserverList)); Observer *obs = inObserverList->mObserverList; do { if (obs->mCallback == inObserver && obs->mClosure == inClosure) { PR_REMOVE_LINK(ObserverLinks(obs)); if (obs == inObserverList->mObserverList) { Observer* next = NextObserver(obs); inObserverList->mObserverList = (next != obs) ? next : NULL; } PR_FREEIF(obs); result = PR_TRUE; break; } obs = NextObserver(obs); } while (obs != tail); } return result; } /* If observer notification is enabled for this XP_Observable, this will call each registered observer proc, passing it the given message and void* ioData, in addition to the observer's closure void*. There is no defined order in which observers are called. */ void XP_NotifyObservers( XP_ObserverList inObserverList, XP_ObservableMsg inMessage, void* ioData ) { Observer* obs; Observer *tail; Observer *temp; PRBool done = PR_FALSE; if ( ! inObserverList->mNotificationEnabled || inObserverList->mObserverList == NULL) { return; } obs = inObserverList->mObserverList; tail = (Observer *) PR_LIST_TAIL(ObserverLinks(obs)); do { if (obs == tail) done = PR_TRUE; (obs->mCallback)(inObserverList->mObservable, inMessage, ioData, obs->mClosure); if (done != PR_TRUE) { temp = inObserverList->mObserverList; /* just in case this callback free this observable entry. */ if (temp != obs) { obs = temp; tail = (Observer *) PR_LIST_TAIL(ObserverLinks(obs)); } else obs = NextObserver(obs); } } while (done != PR_TRUE); } /* When called, subsequent calls to XP_NotifyObservers will do nothing until XP_EnableObserverNotification is called. */ void XP_DisableObserverNotification( XP_ObserverList inObserverList ) { inObserverList->mNotificationEnabled = PR_FALSE; } /* Enables calling observers when XP_NotifyObservers is invoked. */ void XP_EnableObserverNotification( XP_ObserverList inObserverList ) { inObserverList->mNotificationEnabled = PR_TRUE; } /* Returns true if observer notification is enabled. */ PRBool XP_IsObserverNotificationEnabled( XP_ObserverList inObserverList ) { return inObserverList->mNotificationEnabled; }
Drawbacks
Abstract coupling between subject and observer: All a subject knows is that there are observers. Since the subject and observer aren't tightly coupled, the two objects can be on different layers in a system.
Support for broadcast communication: When a subject sends out a notification it does not specify a receiver. The subject just broadcasts its current change of state.
Unexpected updates: Since observers do not know about the other observers which may or may not be watching the subject, there may be a lot of updates to the observes and the objects which could result in an endless loop.
Links
http://www.dofactory.com/Patterns/PatternObserver.aspx#_self1
http://en.wikipedia.org/wiki/Observer_pattern
http://sern.ucalgary.ca/courses/SENG/609.04/W98/lamsh/observerLib.html
http://www.java2s.com/Code/Java/Design-Pattern/Observer-Pattern.htm