Never confuse education with intelligence, you can have a PhD and still be an idiot.
- Richard Feynman -



Design patterns - Observer

From Juneday education
Revision as of 18:32, 30 November 2016 by Rikard (Talk | contribs) (Another example: Swing example, stupid but efficient??)

Jump to: navigation, search

Description

In this chapter, we'll discuss the Observer pattern.

Aim of the pattern

The intent of this pattern is to allow for a one-to-many relationship between one object with important state, and many interested objects, subscribing to changes in the important object. It is often described in terms of a "Subject" object which is observable by many "Observers" (or sometimes "Listeners").

Problem description

We often have a situation where something central (the Subject) in our application changes its state, and many components (observers) would be interested to hear about this. The Subject should be loosely coupled with any amount of observers and treat them uniformly. But the actual observers can be totally unrelated objects of very different classes.

class SuperImportantClass{
  List interestedObjects; // what type should we use here?
  // how do we populate the list with interested objects?

  // Times they are a changing...(but Robert stays the same)
  void stateChangesForSomeReason(){
    for(Object o : interestedObjects){
      //tell them about the changes, but how?
    }
  }
}

As usual, we don't want our classes to know too much about each other, so that things stay on a high abstraction level and things can vary and change without too much impact between classes.

Solution - applying the Observer Pattern

The solution to this problem is to follow the Observer pattern. As usual, there are a few variations of this pattern but we'll explain a common design for it. In order to keep the classes (Subject and Observers) loosely couples (knowing little about each others implementation details), we define two interfaces. We're gonna call the interface for the Subject Observable and the interface for the observers Observer. The Observable interface declares methods every Subject object must have:

  • public void add(Observer o); // an observer wants to register
  • public void delete(Observer o); // unregister observer
  • public void notifyObservers(); // notify all observers

The Observer interface declares (perhaps, but at least in our version) only one method:

  • public void update(Observable o); // this will be called when the Subject wants you to be notified

By using interfaces, we can keep the Subject in happy ignorance about the observers, as well as every observer happily ignorant about the details about the Subject they are observing. We obtain a loose coupling between the actual concrete classes. As long as the Subject adheres to the specification of the Observable interface, and as long as all the observers adhere to the specification of the Observer interface, they can "talk" to each other without knowing too much about the details.

Disclaimer: Note, that in our examples, actually, the observers are kind of coupled with the Observable (they know exactly what class it is made from, and even down-cast the Observable to that type!), so this feature isn't bilateral in our examples. But at least, you can see that the Observable Subject isn't aware of the classes and types of its observers other than that they are adhering to the Observer interface.

Another example

Looking at the Java API is always useful when learning techniques for designing APIs and classes. Often they are quite well designed. The frameworks for graphical interfaces, Swing and AWT use the observer pattern for the event model. Components of the framework provide methods for adding "listeners" (observers) so that objects can get a call-back when an event happens (for instance when someone clicks a button).

This is a version of the observer pattern! A button offers "listeners" (any amount of them) to register themselves with the button, so that the button, when clicked, can notify the listeners that a button click event has occurred.

This means that more than one object can listen to a button and act when the button is clicked. And, actually, the same object can also register with more than one component if the same action should be taken for events from two separate components.

Our example in the video lecture shows that a panic button can have any amount of listeners registered to it. If some one clicks the panic button, all the listeners are notified and run some action. The example simulates (in text, of course, as you have understood by now, we like old school examples), what could happen if someone clicks the panic button of - say for instance - a hotel. The various listeners send of an alarm message in different languages. This allows us to add more languages later on. The code for the actual button need not change - as long as a new listener gets registered, it will get notified when the button is clicked.

The Swing designers have made it simple for us to add more listeners to for instance such a button. They decoupled the action to be taken from the button itself and allowed for multiple actions to be taken. By using an interface ActionListener which simply declares one method actionPerformed(), they make it simple for the button to notify all registered listeners - they all have such a method (or they couldn't register!).

Here's the stupid example Swing code (pay no attention to the uselessness of this application - it is just an example of registering multiple listeners to one single button):

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class ButtonObservers{
  private JButton theButton;
  private JFrame frame;
  
  public ButtonObservers(){
    initComponents();
    layoutComponents();
    frame.setVisible(true);
  }
  private void initComponents(){
    frame = new JFrame("Layout changer");
    frame.setLayout(new BorderLayout());
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setPreferredSize(new Dimension(500,500));
    theButton = new JButton("Hälärm");
    theButton.setFont(new Font("Arial", Font.PLAIN, 40));
    // Add a German panic button listener
    theButton.addActionListener(new ActionListener(){
        @Override
        public void actionPerformed(ActionEvent ae){
          System.out.println("Hilfe!");
        }
      });
    // Add a Swedish panic button listener
    theButton.addActionListener(new ActionListener(){
        @Override
        public void actionPerformed(ActionEvent ae){
          System.out.println("Hjälp!");
        }
      });
    // Add an English panic button listener
    theButton.addActionListener(new ActionListener(){
        @Override
        public void actionPerformed(ActionEvent ae){
          System.out.println("Help!");
        }
      });
    // Add a Spanish panic button listener
    theButton.addActionListener(new ActionListener(){
        @Override
        public void actionPerformed(ActionEvent ae){
          System.out.println("Socorro!");
        }
      });
  }
  private void layoutComponents(){
    frame.add(theButton, BorderLayout.CENTER);
    frame.pack();
  }
  public static void main(String[] args){
    new ButtonObservers();
  }
}
/* TODO: some example code */

Exercises

Task 1 - TODO: write an exercise, man!

/* TODO: some source code here */

Challenge - 1

TODO: Challenge 1

Challenge - 2

TODO: Challenge 2

Hints

TODO: If necessary, create hints for the exercise.

Suggested solutions

Expand using link to the right to see the full content.

You can download source code including suggested solutions TODO: here (github). But here are the source code for the suggested solutions:

/* TODO: suggested solution sources */

Chapter links

Vimeo channel

Source code

  • Github [TODO: repository]

Books this chapter is a part of

Further reading

Note: the articles above use slightly different designs. And their designs vary slightly compared to our version, but the general idea is the same.

Book TOC | previous chapter | TODO: next