Observer Pattern

20 May 2015

Observer Pattern has been around Java since version JDK 1.0. One of the nice thing about having it inside the language is you only have to subclass it and just worry about its implementation logic.

Here are the key benefits of the observer pattern.

This is from one of our exercises which is a card game called Tehi. This is not the complete program but what it shows here is how are you going to use the observer pattern. First you will notice CardDeck extends Observable and the abstract CardViewer implements Observer. The concrete classes FutureCardsViewer and PastCardViewers then has update method which is the glue that attached them to the Observable.

Card Deck

package com.tehi;

import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.Observable;

public final class CardDeck extends Observable {

	private LinkedList<PlayingCard> cardStack = new LinkedList<>();

	/**
	 * Initially the cards are ordered by suite and rank.
	 */
	CardDeck(){
		Rank[] rankArray = Rank.values();
		Suite[] suiteArray = Suite.values();
		for (Suite suite : suiteArray) {
			for (Rank rank : rankArray) {
				cardStack.add(new PlayingCard(suite, rank));
			}
		}
	}

	/**
	 * Deals 1 card from the top of the deck
	 */
	PlayingCard deal(){
		PlayingCard card;
		try {
			card = cardStack.pop();
		} catch (NoSuchElementException e) {
			throw new IllegalStateException("Ran out of cards!");
		}
		setChanged();
		notifyObservers(getRanksDealt());
		return card;
	}

	/**
	 * Gets the ith card, 0 <= i < 52. The
	 * card is not removed.
	 * @param index
	 * @return
	 */
	PlayingCard peek(int index){
		if(index<0 || index>cardStack.size()-1){
			return null;
		}
		return cardStack.get(index);
	}

	/**
	 * Shuffles the stack of cards.
	 */
	void shuffle(){
		Collections.shuffle(cardStack);
	}		

	/**
	 * Deals n cards from the top of the deck
	 * @param n
	 */
	PlayingCard[] deal(int n){
		PlayingCard[] result = new PlayingCard[n];
		for(int i=0; i<n;i++){
			result[i]=deal();
		}
		return result;
	}

	/**
	 * Gets the ranks of the cards left in the deck.
	 * @return an int[] containing the ranks of the remaining cards
	 */
	int[] getRanksLeft(){
		int[] result = new int[13];
		Arrays.fill(result, 0);
		for (PlayingCard card : cardStack) {
			int rankNUmber = card.getRank().getRankNumber()-1;
			result[rankNUmber]++;
		}
		return result;
	}

	/**
	 * Gets the ranks of the cards no longer in the deck.
	 * @return an int[] containing the ranks of the already dealt cards
	 */
	int[] getRanksDealt(){
		int[] result = new int[13];
		int[] dealt = getRanksLeft();
		for (int i=0; i<dealt.length;i++) {
			result[i]=4-dealt[i];
		}
		return result;
	}

	/**
	 * Accessor to the number of cards left in the deck.
	 * @return an int, the size of the deck.
	 */
	public int size(){
		return cardStack.size();
	}

	/**
	 * Checks if the deck is empty.
	 * @return true if the deck has no cards, false otherwise.
	 */
	public boolean isEmpty() {
		return (size()==0)?true:false;
	}
}

Cards Viewer

package com.tehi;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridLayout;
import java.util.Observer;

import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JPanel;

@SuppressWarnings("serial")
public abstract class CardsViewer extends JPanel implements Observer {

	private JPanel centerPanel = new JPanel();
	protected JLabel[][] tiles = new JLabel[4][13];

	public CardsViewer() {
		setBorder(BorderFactory.createLineBorder(Color.BLACK));
		setLayout(new BorderLayout());
		centerPanel.setLayout(new GridLayout(4, 13));
		for (int i = 0; i < 4; i++) {
			for (int j = 0; j < 13; j++) {
				tiles[i][j] = new JLabel("", JLabel.CENTER);
				tiles[i][j].setText(Rank.convertIntToRank(j + 1).getName());
				centerPanel.add(tiles[i][j]);
			}
		}
		add(centerPanel, BorderLayout.CENTER);
	}

	/**
	 * Updates the GUI.
	 * @param data an int[] with ranks of cards.
	 */
	protected abstract void updateChart(int[] data);
}

Future Cards Viewer

package com.tehi;

import java.util.Observable;

public class FutureCardsViewer extends CardsViewer {

	/**
	 * Chart that displays cards that have not been dealt.
	 */
	private static final long serialVersionUID = 1L;

	/**
	 * The Observable is an instance of CardDeck and
	 * the Object is an int[] with the ranks that
	 * have already been dealt from the deck.
	 */
	@Override
	public void update(Observable observable, Object arg) {
		CardDeck deck = (CardDeck)observable;
		int[] ranksNotDealt = deck.getRanksLeft();		
		updateChart(ranksNotDealt);
	}

	/**
	 * {@inheritDoc}
	 */
	protected void updateChart(int[] ranksNotDealt) {
		for (int j = 0; j < ranksNotDealt.length; j++) {
			int numberOfcardsDealt = ranksNotDealt[j];
			for (int i = 0; i < 4 - numberOfcardsDealt; i++) {
				tiles[i][j].setText("");
			}
			for (int i = 4 - numberOfcardsDealt; i < 4; i++) {
				tiles[i][j].setText(Rank.convertIntToRank(j + 1).getName());
			}			
		}
	}
}

Past Cards Viewer

package com.tehi;

import java.util.Observable;

public class PastCardsViewer extends CardsViewer {

	/**
	 * Chart that displays cards that have been dealt.
	 */
	private static final long serialVersionUID = 1L;

	/**
	 * The Observable is an instance of CardDeck and
	 * the Object is an int[] with the ranks that
	 * have already been dealt from the deck.
	 */
	@Override
	public void update(Observable o, Object arg) {
		int[] ranksDealt = (int[]) arg;
		updateChart(ranksDealt);
	}

	/**
	 * {@inheritDoc}
	 */
	protected void updateChart(int[] ranksDealt) {
		for (int j = 0; j < ranksDealt.length; j++) {
			int numberOfcardsDealt = ranksDealt[j];
			for (int i = 0; i < 4 - numberOfcardsDealt; i++) {
				tiles[i][j].setText("");
			}
			for (int i = 4 - numberOfcardsDealt; i < 4; i++) {
				tiles[i][j].setText(Rank.convertIntToRank(j + 1).getName());
			}			
		}
	}
}