We are well into this whole patterns thing now, picking up lots of new lingo, and becoming better at communicating with other developers. Next on the list: the observer pattern. This is an important pattern to use to help prevent tight coupling and keep code separated into discrete objects. This pattern allows communication between two objects at particular times without each object needing to be too tightly coupled to the other.
The Observer in the Physical World
The idea behind the observer is like a subscription. Do you have a newspaper or magazine subscription? Have you signed up for cell phone texts from your favorite band when they have new tour dates? The act of you signing up with the newspaper, the magazine, or the band, is you saying you want a subscription, or that you want to become an observer. When the subject (newspaper, magazine, band) has something new to share (on a schedule or not) they run down their subscriber list, and notify the observers by mail, or text.
The Observer in the Software World
On the software side, the idea is the same. Your subject needs to provide a way to take subscriptions, hold a list of observers, and when a particular state changes, notify them. There is usually a couple of methods on the subject that takes in an interface that is used for getting on or off the subscription list. On this interface is also the “call back” method. This is the method that the subject will execute when it wants to inform the observers of state changes.
Do you have a picture?
Here is a class diagram for our first example. We have two classes and an interface. The interface, NumberObserver is the piece that connects the subject and the observer together. The NumberHolder class has methods to add and remove our observer from an internal list. The NumberDisplayer sets this object and the notify method on NumberHolder. The way it was implemented here was that NumberDisplayer implemented the interface. This is not the only way to do it. NumberDisplayer could create an inner class, or there could be an external class that implements the NumberObserver interface. It doesn’t matter. All that does matter is that NumberDisplayer passes a NumberObserver object to NumberHolder that implements the notifyNewNumber method. When NumberHolder changes it’s number, it then notifies all the observers by looping over the list it has of observers and executing the notifyNewNumber method on each one.
The NumberObserver is very basic. Just one method is defined here. The value that will be sent by the subject is the new number.
public interface NumberObserver {
public void notifyNewNumber(int pNewValue);
}
}
The NumberHolder doesn’t have much going on either. Most of the code is just to take care of the observers. Note the add, remove, and update observer methods.
public class NumberHolder {
private int number;
private List observerList;
public NumberHolder() {
number = 10;
observerList = new ArrayList();
}
public void setNumber(int pNumber) {
System.out.println("Number set to " + pNumber);
number = pNumber;
updateObservers();
}
public void addObserver(NumberObserver pObserver) {
System.out.println("Adding observer:" + pObserver);
observerList.add(pObserver);
}
public void removeObserver(NumberObserver pObserver) {
System.out.println("Removing observer:" + pObserver);
observerList.remove(pObserver);
}
private void updateObservers() {
for (NumberObserver aObserver : observerList) {
aObserver.notifyNewNumber(number);
}
}
}
And finally, the NumberDisplayer class that implements the NumberObserver interface.
public class NumberDisplayer implements NumberObserver {
private String name;
public NumberDisplayer(String pName) {
name = pName;
}
public void notifyNewNumber(int pNewValue) {
System.out.println(name + " is now displaying " + pNewValue);
}
}
NumberHolderTest puts these three classes together. It creates several NumberDisplayer classes and adds and removes objects from the NumberHolder as well as updating the value. Run this to see the observer at work.
public class NumberHolderTest {
public static void main(String[] args) {
NumberHolder aNumberHolder = new NumberHolder();
// we can set the number at any time. Nothing happens because we don't
// have any observers registered.
aNumberHolder.setNumber(10);
// Each NumberDisplayer will register with the NumberHolder
NumberDisplayer aObserverAndDisplayerOne = new NumberDisplayer("One");
aNumberHolder.addObserver(aObserverAndDisplayerOne);
NumberDisplayer aObserverAndDisplayerTwo = new NumberDisplayer("Two");
aNumberHolder.addObserver(aObserverAndDisplayerTwo);
NumberDisplayer aObserverAndDisplayerThree = new NumberDisplayer("Three");
aNumberHolder.addObserver(aObserverAndDisplayerThree);
// Each NumberDisplayer should get notified of the change
aNumberHolder.setNumber(20);
// Remove two observers
aNumberHolder.removeObserver(aObserverAndDisplayerOne);
aNumberHolder.removeObserver(aObserverAndDisplayerTwo);
// Now only one observer is left
aNumberHolder.setNumber(30);
}
}
The main idea here is that we can keep the two objects loosely coupled, with the one interface joining them together. The way this is done can vary a bit. In this particular case we have chosen to pass the new number. You could pass a different interface that exposed methods on the subject. You could just have a method that doesn’t pass anything if just need to know that something happened.
Let’s look at another example, this time using our Animal classes.
Defining the problem
We have a zoo. A zoo contains many cages, each cage contains several animals. The zoo keepers would like to have an up-to-date tally of all the animals in the zoo. Every time animals are added or removed from the cages, they want to know the totals. Let’s look at this without the observer pattern first.
No Observer Pattern
public class Cage1 {
// List to hold our animals
private List animalList;
public Cage1() {
animalList = new ArrayList();
}
public void insertAnimal(T pAnimal) {
animalList.add(pAnimal);
System.out.println("\n" + pAnimal.getName() + " " + pAnimal.getType() + " is added to the cage.");
}
public void removeAnimal(T pAnimal) {
animalList.remove(pAnimal);
System.out.println("\n" + pAnimal.getName() + " " + pAnimal.getType() + " is removed from the cage.");
}
public void clearCage() {
animalList.clear();
}
public List getAnimals() {
return animalList;
}
}
public class Zoo1 {
private List> cages;
public Zoo1() {
cages = new ArrayList>();
}
public void addCage(Cage1 extends Animal> pCage) {
cages.add(pCage);
}
public void removeCage(Cage1 extends Animal> pCage) {
cages.remove(pCage);
}
public void doCageUpdated() {
Map aAnimalCounts = getAnimalCounts();
displayAnimalCounts(aAnimalCounts);
}
public Map getAnimalCounts() {
Map aAnimalCounts = new HashMap();
for (Cage1 extends Animal> aCage : cages) {
List extends Animal> aAnimalList = aCage.getAnimals();
int aCount = aAnimalList.size();
if (aCount > 0) {
String aType = aAnimalList.get(0).getType();
Integer aTypeCount = aAnimalCounts.get(aType);
if (aTypeCount == null) {
aTypeCount = new Integer(aCount);
} else {
aTypeCount = new Integer(aTypeCount.intValue() + aCount);
}
aAnimalCounts.put(aType, aTypeCount);
}
}
return aAnimalCounts;
}
public void displayAnimalCounts(Map animalCounts) {
Set aAnimalSet = animalCounts.keySet();
if (aAnimalSet == null || aAnimalSet.isEmpty()) {
System.out.println("\nZoo is currently empty.");
} else {
System.out.println("\nCurrent zoo animal counts:");
}
for (String aType : aAnimalSet) {
Integer aTypeCount = animalCounts.get(aType);
System.out.println("There are " + aTypeCount.intValue() + " of type " + aType);
}
}
}
See Cage1.java. Pretty simple. Just a simple list, and the methods to support getting data in and out of the list. Now look at Zoo1.java. This class contains the cages in a List. It also has the methods that can look at all the cages and create the totals for each animal type.
There is also a method doCageUpdated that must be called everytime an animal is added to a cage. The way we have this set up so far, there is no way for the zoo to know when an animal is added to a cage. A cage knows where an animal is added, but the cage doesn’t know about the zoo. It is contained within the zoo, but there is no coupling between the cage, and the zoo. We could insert a reverence for the zoo into the cage when the cages are added to the zoo, but we try to avoid this circular coupling.
public class ZooTest1 {
public static void main(String[] args) {
Zoo1 aZoo = new Zoo1();
Cage1 aBirdCage = new Cage1();
aZoo.addCage(aBirdCage);
aBirdCage.insertAnimal(new Bird());
// tell zoo to check counts and update display
aZoo.doCageUpdated();
aBirdCage.insertAnimal(new Bird());
// tell zoo to check counts and update display
aZoo.doCageUpdated();
Cage1 aMammalCage = new Cage1();
aZoo.addCage(aMammalCage);
// tell zoo to check counts and update display
aZoo.doCageUpdated();
Mammal aFred = new Mammal("Fred");
aMammalCage.insertAnimal(aFred);
// tell zoo to check counts and update display
aZoo.doCageUpdated();
Cage1 aDogCage = new Cage1();
aZoo.addCage(aDogCage);
aDogCage.insertAnimal(new Mammal("Rover"));
// tell zoo to check counts and update display
aZoo.doCageUpdated();
aDogCage.insertAnimal(new Mammal("Spot"));
// tell zoo to check counts and update display
aZoo.doCageUpdated();
aMammalCage.removeAnimal(aFred);
// tell zoo to check counts and update display
aZoo.doCageUpdated();
aBirdCage.clearCage();
// tell zoo to check counts and update display
aZoo.doCageUpdated();
aDogCage.clearCage();
// tell zoo to check counts and update display
aZoo.doCageUpdated();
}
}
What we are forced to do, and must rely on, is that the developer writing the program will call the doCageUpdated() method on the zoo object every time an animal is added to a cage. See ZooTest1.java. This is not good programming practice.
Implementing the Observer Pattern
public class Cage2 {
// List to hold our animals
private List animalList;
// List to hold our observers
private List cageObservers;
public Cage2() {
animalList = new ArrayList();
cageObservers = new ArrayList();
}
public void insertAnimal(T pAnimal) {
animalList.add(pAnimal);
System.out.println("\n" + pAnimal.getName() + " " + pAnimal.getType() + " is added to the cage.");
notifyObservers();
}
public void removeAnimal(T pAnimal) {
animalList.remove(pAnimal);
System.out.println("\n" + pAnimal.getName() + " " + pAnimal.getType() + " is removed from the cage.");
notifyObservers();
}
public void clearCage() {
animalList.clear();
notifyObservers();
}
public List getAnimals() {
return animalList;
}
public void addCageObserver(CageObserver pCageObserver) {
cageObservers.add(pCageObserver);
}
public void removeCageObserver(CageObserver pCageObserver) {
cageObservers.remove(pCageObserver);
}
private void notifyObservers() {
for (CageObserver aCageObserver : cageObservers) {
aCageObserver.doCageUpdated();
}
}
}
public class Zoo2 implements CageObserver {
private List> cages;
public Zoo2() {
cages = new ArrayList>();
}
public void addCage(Cage2 extends Animal> pCage) {
pCage.addCageObserver(this);
cages.add(pCage);
}
public void removeCage(Cage2 extends Animal> pCage) {
pCage.removeCageObserver(this);
cages.remove(pCage);
}
public void doCageUpdated() {
Map aAnimalCounts = getAnimalCounts();
displayAnimalCounts(aAnimalCounts);
}
public Map getAnimalCounts() {
Map aAnimalCounts = new HashMap();
for (Cage2 extends Animal> aCage : cages) {
List extends Animal> aAnimalList = aCage.getAnimals();
int aCount = aAnimalList.size();
if (aCount > 0) {
String aType = aAnimalList.get(0).getType();
Integer aTypeCount = aAnimalCounts.get(aType);
if (aTypeCount == null) {
aTypeCount = new Integer(aCount);
} else {
aTypeCount = new Integer(aTypeCount.intValue() + aCount);
}
aAnimalCounts.put(aType, aTypeCount);
}
}
return aAnimalCounts;
}
public void displayAnimalCounts(Map animalCounts) {
Set aAnimalSet = animalCounts.keySet();
if (aAnimalSet == null || aAnimalSet.isEmpty()) {
System.out.println("\nZoo is currently empty.");
} else {
System.out.println("\nCurrent zoo animal counts:");
}
for (String aType : aAnimalSet) {
Integer aTypeCount = animalCounts.get(aType);
System.out.println("There are " + aTypeCount.intValue() + " of type " + aType);
}
}
}
A better way is to implement the observer pattern. Look at Cage2.java, Zoo2.java, and ZooTest2.java. Looking at Cage2.java you can see that the Cage2 class now has another list, the list of CageObserver’s. Now, when an animal is added to the cage, the cage informs anyone that is listening that the cage count has changed. In our case, we just send a message that the cage is updated, but in a different design you may have passed the Cage2 object its self, or an interface, or another object that indicated the type of event (add, remove, clear) and the number of animals that it evolved. That is all up to your design. They would all fit the observer pattern.
public class ZooTest2 {
public static void main(String[] args) {
Zoo2 aZoo = new Zoo2();
Cage2 aBirdCage = new Cage2();
aZoo.addCage(aBirdCage);
aBirdCage.insertAnimal(new Bird());
aBirdCage.insertAnimal(new Bird());
Cage2 aMammalCage = new Cage2();
aZoo.addCage(aMammalCage);
Mammal aFred = new Mammal("Fred");
aMammalCage.insertAnimal(aFred);
Cage2 aDogCage = new Cage2();
aZoo.addCage(aDogCage);
aDogCage.insertAnimal(new Mammal("Rover"));
aDogCage.insertAnimal(new Mammal("Spot"));
aMammalCage.removeAnimal(aFred);
aBirdCage.clearCage();
aDogCage.clearCage();
}
}
Now look at the ZooTest2 class. Much better. No longer do we have to tell the zoo to display the data, it is done automatically. This may not seem that worthwhile here, but the process for adding cages to the zoo may not be connected with the process of adding animals to anyone cage. Keeping these things separate allows for more flexibility.
Swinging the Observer Pattern
We have already used the observer pattern in a previous lesson. Way back when we covered classes we looked at creating a Gui and implemented a WindowListener. We didn’t discuss it back then, but we were implementing the Observer Pattern. That’s right, every time we register an action listener of some sort in AWT, or Swing, we are registering an Observer with a Subject. Go back and look at Gui1 to Gui4. These are 4 different ways of creating the Observing class. As an example, here is Gui3.java.
public class Gui3 extends JFrame {
public Gui3() {
setTitle("Gui3");
setSize(300, 300);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.out.println("Closeing Gui3");
System.exit(0);
}
});
}
public static void main(String[] args) {
JFrame aFrame = new Gui3();
aFrame.setVisible(true);
}
}
So keep this in mind. If you have written a GUI program, it is almost certain that you have implemented the Observer pattern. Swing depends heavily on it. That is how your visual components can react when users press buttons, move focus, and close windows. This way, you don’t have to create a loop of code that checks for a mouse click, and then determine which button the mouse was over, and then directs the program flow to specific code handling sections. You simple register with each button to be informed when it is clicked. And remember, several objects can register to listen at the same time. Because of this, Java even has a solution you can implement, called the EventListenerList. You can use this list in your classes to implement event listeners. Why reinvent it your self if it is already provided. This class supplies the underlying list, and methods to add and remove EventListeners to and from the List. It doesn’t supply a method to notify listeners in the list, but it does provide a method (getListeners()) that can take in a class type parameter so that you can ask the list to return just the listeners of a particular type. If you find you need to implement an observer event list, check this class out.
Great Tutorial on explaining about Observer Pattern. Nice one..