Difference between revisions of "Observer"

From CDOT Wiki
Jump to: navigation, search
(Structure)
 
(7 intermediate revisions by the same user not shown)
Line 2: Line 2:
  
 
== Observer Pattern ==
 
== 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.
+
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 ==
 
== UML Example ==
 
[[Image:Observer.gif]]
 
[[Image:Observer.gif]]
 
+
[http://www.dofactory.com/Patterns/Diagrams/observer.gif]
  
 
== Structure ==
 
== Structure ==
Subject: Knows its observers and provides an interface for attaching and detaching observers
+
<b>Subject</b>: Knows its observers and provides an interface for attaching and detaching observers
 
<br>
 
<br>
Observer: Defines an updating interface for objects that should be notified
+
<b>Observer</b>: Defines an updating interface for objects that should be notified
 
<br>
 
<br>
ConcreteSubject: Stores states of ConcreteObservers objects. Sends a notification when a change has occurred
+
<b>ConcreteSubject</b>: Stores states of ConcreteObservers objects. Notifies other objects when a change has been made.
 
<br>
 
<br>
ConcreteObserver: Maintains a reference to the ConcreteSubjects object. Synchronizes itself with the ConcreteSubject's state.
+
<b>ConcreteObserver</b>: Holds a reference to the ConcreteSubjects object.
  
 
== Code Examples ==
 
== Code Examples ==
Line 470: Line 470:
 
     }
 
     }
 
   }
 
   }
 +
}
 +
</pre>
 +
 +
== Real World Example ==
 +
Here is some code from the Mozilla Internet Browser V 1.7a
 +
<pre>
 +
/* -*- 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;
 
}
 
}
 
</pre>
 
</pre>

Latest revision as of 23:21, 13 April 2007

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