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); }