Observer

From CDOT Wiki
Revision as of 00:21, 14 April 2007 by Bbarcick (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

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

Observer.gif [1]

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