Observers and listeners

The Observer pattern (also known as Listener, or Publish-Subscribe) is particularly useful for graphical applications.

The general idea is that one or more objects (the subscribers) register their interest in being notified of changes to another object (the publisher). The subscribers can also change the state of the publisher, although this is not obligatory (one might call these "active" observers, as opposed to "passive" ones, which do not change the state of the publisher). Interfaces are used to ensure that publishers and subscribers have only minimal knowledge of each other.

When using Swing, there is a choice among using Observable and Observer , or one of the many XXXListener interfaces which apply to GUI components.

Although Observer is indeed an interface, Observable is neither an interface nor an abstract base class: it's a concrete class. This is very unusual. Almost all implementations of Design Patterns define the important items as either interfaces or abstract base classes, not as concrete classes.

Example

The CurrentPortfolio class is a non-graphical class which encapsulates data regarding a set of stocks - a stock portfolio. It's the central abstraction of its application, and a number of other classes need to interact with it, both as active and passive listeners. Since it's a non-graphical class, it extends Observable. Note how it calls:

Observable.setChanged()
whenever its underlying data changes, informing its listeners of changes to the portfolio.

The FileSaveAction class is an "active" Observer of the CurrentPortfolio: it both reacts to changes in the CurrentPortfolio, and changes it. It reacts to CurrentPortfolio in its update method, where it enables the action only when the CurrentPortfolio has unsaved edits. When FileSaveAction itself is executed, on the other hand, its actionPerformed method acts upon CurrentPortfolio by changing its state to "no unsaved edits".

package hirondelle.stocks.file;

import java.awt.event.*;
import javax.swing.*;
import java.util.*;
import hirondelle.stocks.portfolio.PortfolioDAO;
import hirondelle.stocks.portfolio.CurrentPortfolio;
import hirondelle.stocks.util.ui.UiUtil;
import java.util.logging.Logger;
import hirondelle.stocks.util.Util;

/**
* Save the edits performed on the {@link CurrentPortfolio}, and update the display 
* to show that the <tt>CurrentPortfolio</tt> no longer needs a save.
*/
public final class FileSaveAction extends AbstractAction implements Observer {

  /**
  * Constructor.
  *  
  * @param aCurrentPortfolio is to be saved by this action.
  */
  public FileSaveAction(CurrentPortfolio aCurrentPortfolio) {
    super("Save", UiUtil.getImageIcon("/toolbarButtonGraphics/general/Save")); 
    fCurrentPortfolio = aCurrentPortfolio;
    fCurrentPortfolio.addObserver(this);
    putValue(SHORT_DESCRIPTION, "Save edits to the current portfolio");
    putValue(
      ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.CTRL_MASK)
    );
    putValue(LONG_DESCRIPTION, "Save edits to the current portfolio");
    putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_S));    
  }

  @Override public void actionPerformed(ActionEvent e) {
    fLogger.info("Saving edits to the current portfolio.");
    PortfolioDAO portfolioDAO = new PortfolioDAO();
    portfolioDAO.save(fCurrentPortfolio.getPortfolio());
    fCurrentPortfolio.setNeedsSave(false);
    fCurrentPortfolio.notifyObservers();
  }  
  
  /**
  * Synchronize the state of this object with the state of the 
  * <tt>CurrentPortfolio</tt> passed to the constructor.
  *
  * This action is enabled only when the <tt>CurrentPortfolio</tt> is titled and 
  * needs a save.
  */
  @Override public void update(Observable aPublisher, Object aData) {
    setEnabled(fCurrentPortfolio.getNeedsSave() && !fCurrentPortfolio.isUntitled());
  } 
  
  // PRIVATE 
  private CurrentPortfolio fCurrentPortfolio;
  private static final Logger fLogger = Util.getLogger(FileSaveAction.class);
}
 

See Also :
Filter table rows