Home CPSC 330

Design Patterns 3

Overview

Today we will look at behavioral design patterns - those that deal with the way the running program works.


Command

The goal of the command pattern is to encapsulate a method call inside of an object so that it can be invoked at some later point.

This allows an arbitrary piece of code to be executed later without the caller needing to know the code or the data it operates on.

The following example demonstrates the command pattern. It creates an interface called Command that allows code to execute commands without needing to know what the command does:


// CommandExample.java

interface Command {
  public void execute();
}


// this class can store a number of commands
// and execute them in a given order
class CommandOrderer {
  private Command [] commands;
  
  public CommandOrderer(int n) {
    commands = new Command[n];
  }

  public void add(Command c, int index) {
    commands[index] = c;
  }

  public void executeAll() {
    for(Command c : commands) {
      c.execute();
    }
  }
}

// commands can store data, have constructors etc.
class PrintCommand implements Command {
  PrintCommand(String m) {
    mesg = "The message is: " + m;
  }

  public void execute() {
    System.out.println(mesg);
  }

  String mesg;
}


public class CommandExample {
  public static void main(String args[]) {
    CommandOrderer orderer = new CommandOrderer(3);

    // add a couple PrintCommands
    orderer.add(new PrintCommand("first one added!"), 2);
    orderer.add(new PrintCommand("second one added!"), 1);
    
    // add a command with an anonymous class
    orderer.add(new Command() {
      public void execute() {
        System.out.println("This command was added last but is executed first!");
      }
    }, 0);

    // now execute all of them!
    orderer.executeAll();
  }
}

This can be used for:


Iterator

The iterator pattern is used to allow code to traverse a data structure without needing to know what type of data structure it is.

The idea is that there is an interface for iterating through a collection of items. Code that needs to iterate some collection can use the iterator interface to not have to worry about how the collection is actually structured.

This pattern is used in the C++ standard template library (STL), as well as the Java collections library with the Iterator interface.

This example shows how we can use this interface to iterate through data structures:


import java.util.*;

class Algorithms {
  public static int count(Iterator start) {
    int i = 0;

    // loop through using the iterator interface
    Iterator current = start;
    while(current.hasNext()) {
      i++;
      current.next();
    }

    return i;
  }
}

public class IteratorExample {
  public static void main(String args[]) {
    // use with an arraylist
    ArrayList<Integer> nums = new ArrayList<Integer>();
    for(int i = 0; i < 100; i++) {
      nums.add(i);
    }
    System.out.printf("There are %d items in the array.\n", Algorithms.count(nums.iterator()));


    // use with a linked list
    LinkedList<Integer> nums2 = new LinkedList<Integer>();
    for(int i = 0; i < 100; i++) {
      nums2.add(i);
    }
    System.out.printf("There are %d items in the linked list.\n", Algorithms.count(nums2.iterator()));
  }
}

There is a related interface in Java called the Iterable interface. This interface allows all data structures that can be traversed to be treated the same. We can make our own data structures fit in with this pattern by making them implement this interface.

This example creates a class that allows us to iterate over the English alphabet without needing to store the entire alphabet. The AlphabetIterator class implements the Iterator interface, and the Alphabet class implements Iterable:


// a class for iterating through the alphabet
class AlphabetIterator implements Iterator {
  public AlphabetIterator(boolean uppercase) {
    if(uppercase) {
      current = 'A';
    } else {
      current = 'a';
    }
    upper = uppercase;
  }

  public boolean hasNext() {
    if(upper) {
      return current <= 'Z';
    } else {
      return current <= 'z';
    }
  }

  public Character next() {
    Character val = new Character(current);
    current++;
    return val;
  }

  // this one is optional
  public void remove() {
    // you can't change the alphabet!
  }

  private char current;
  private boolean upper;
}



// a convenience class for dealing with all of the letters of
// the alphabet
class Alphabet implements Iterable {
  Alphabet(boolean uppercase) {
    this.uppercase = uppercase;
  }

  public Iterator iterator() {
    return new AlphabetIterator(uppercase);
  }

  private boolean uppercase;
}

We can then use this data structure with code that works with iterators:


// make a capital alphabet
Alphabet letters = new Alphabet(true);

// count the letters
System.out.printf("There are %d items in the alphabet:\n", Algorithms.count(letters.iterator()));

Java also uses the Iterable interface to support the for-each tyle for loop:


for(Character c : letters) {
  System.out.println(c);
}

Observer

The observer pattern is a design pattern whose goal is to allow a number of objects (the observers) to be notified when some other object (the subject) has changed in some way.

This pattern is used to implement action listeners in the Swing GUI toolkit.

Consider the following program:


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

class ButtonListener implements ActionListener {
  public ButtonListener(String mesg) {
    this.mesg = mesg;
  }

  public void actionPerformed(ActionEvent e) {
    JOptionPane.showMessageDialog(null, mesg);
  }

  private String mesg;
}

public class GuiObserver {
  public static void main(String args[]) {
    // create and set up the window.
    JFrame frame = new JFrame("Button Example!");

    // make the program close when the window closes
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    // add a button object
    JButton button = new JButton("Push Me!");
    button.addActionListener(new ButtonListener("Observer 1"));
    button.addActionListener(new ButtonListener("Observer 2"));
    button.addActionListener(new ButtonListener("Observer 3"));
    button.addActionListener(new ButtonListener("Observer 4"));
    frame.getContentPane().add(button);

    // display the window.
    frame.pack();
    frame.setVisible(true);
  }    
}

This adds four actionListeners to a single button. Each of these are observers to the button.

Each object that implements ActionListener is an observer, and the button is the subject. The observers each get added to the subject, who maintains a list of all its observers. When the subject is changed in some way, it must notify all of its observers.


Strategy

The goal of the strategy pattern is to allow the behavior of an object to be selected at runtime. The idea is that we create an interface for doing some task that can be done in multiple different ways. Then, we fill in an object that implements that interface in the desired way.

An example of where this strategy may be useful is in implementing AI for a game like Pacman. The ghosts have each have different chasing strategies, and each also depends on the level.

We can encapsulate the ghost's strategy for moving in the level in an interface:


interface GhostStrategy {
  public Location move(Location pacman, Level level);
}

Now the ghosts can all be represented with the same class, and take in the strategy they should use as a parameter:


class Ghost {
  Ghost(Color color, GhostStrategy strategy) {
    this.color = color;
    this.strategy = strategy;
  }

  // set the ghosts location on the level
  private void setLocation(Location location) {
    this.location = location;
  }

  // just use the strategy we have been given
  public Location move(Location pacman, Level level) {
    setLocation(strategy.move(pacman, level));
    return location;
  }

  private Color color;
  private Strategy strategy;
  Location location;
}

Now when we create a ghost, we will specify the movement strategy it uses by creating some kind of GhostStrategy object.

Copyright © 2018 Ian Finlayson | Licensed under a Creative Commons Attribution 4.0 International License.