Java Fundamentals Tutorial: Design Patterns

19. Design Patterns

Design Patterns in Java

19.1. What are Design Patterns?

  • The core of solutions to common problems
  • All patterns have: name, the problem they solve (in context), solution, consequences
  • Types of patterns:

    • Creational - abstraction of instantiation process
    • Structural - composition of classes and objects in formation of larger structures
    • Behavioral - abstraction of algorithms and assignment of responsibility between objects

Why reinvent the wheel when you can follow a field-tested blueprint for solving your not-so-unique problem?

Many of the design patterns in use today and described here originate from the famous Gang of Four book: "Design Patterns: Elements of Reusable Object-Oriented Software" by Eric Gamma, Richard Helm, Ralph Johnson, and John Vlissides (Addison-Wesley Publishing Co., 1995; ISBN: 0201633612)

19.2. Singleton

  • Allow only one instance of a given class

    • The class holds a reference to its only instance
    • Lazy (on request) or static initialization (on load)
    • Private constructor to restrict external instantiation
  • Provide global point of access to the instance

    public static ClassName getInstance()
  • Support multiple instances in the future if the requirements change

    • Update getInstance to return multiple (possibly cached) instances if needed

public class MySingleton {
  private static final MySingleton instance = new MySingleton();

  public static MySingleton getInstance() {
    return instance;
  }

  private MySingleton() {} // no client can do: new MySingleton()

  public void doX() {…}
  public void doY() {…}
  public String getA() {return …;}
  public int getB() {return …;}
}

public class Client {
  public void doSomething() {
    // doing something
    MySingleton.getInstance().doX();
    // doing something else
    int b = MySingleton.getInstance().getB();
    // do something with b
  }
}

19.3. Factory Method

  • Define an interface for creating an object
  • Defer the actual creation to subclasses
  • Localizes the knowledge of concrete instances
  • Use when clients cannot anticipate the class of objects to create

Figure 10. Factory Method Pattern

images/FactoryMethodPattern.png

public interface Logger {
  public void error(String msg);
  public void debug(String msg);
}
public class ConsoleLogger implements Logger {
  public void error(String msg) {System.err.println("ERROR: "+ msg);}
  public void debug(String msg) {System.err.println("DEBUG: "+ msg);}
}
public class FileLogger implements Logger {
  private PrintStream out;
  private FileLogger(String file) throws IOException {
    this.out = new PrintStream(new FileOutputStream(file), true);
  }
  public void error(String msg) {out.println("ERROR: "+ msg);}
  public void debug(String msg) {out.println("DEBUG: "+ msg);}
}
public abstract class LoggerFactory {
  public abstract Logger getLogger();
  public static LoggerFactory getFactory(String f) throws Exception {
    return (LoggerFactory ) Class.forName(f).newInstance();
  }
}
public class ConsoleLoggerFactory extends LoggerFactory {
  public Logger getLogger() { return new ConsoleLogger(); }
}
public class FileLoggerFactory extends LoggerFactory { //ignoring expt
  private Logger log = new FileLogger(System.getProperty("log.file"));
  public Logger getLogger() { return log; }
}

19.4. Abstract Factory

  • Interface for creating family of related objects
  • Clients are independent of how the objects are created, composed, and represented
  • System is configured with one of multiple families of products

Figure 11. Abstract Factory Pattern

images/AbstractFactoryPattern.png

public interface Restaurant {
    public Appetizer getAppetizer();
    public Entree getEntree();
    public Dessert getDessert();
}

public interface Appetizer { public void eat(); }
public interface Entree { public void eat(); }
public interface Dessert { public void enjoy(); }

public class AmericanRestaurant implements Restaurant {
    public Appetizer getAppetizer() { return new Oysters(); }
    public Entree getEntree() { return new Steak(); }
    public Dessert getDessert() { return new CheeseCake(); }
}
public class ItalianRestaurant implements Restaurant {
    public Appetizer getAppetizer() { return new Pizzette(); }
    public Entree getEntree() { return new Pasta(); }
    public Dessert getDessert() { return new Gelato(); }
}

public class Oysters implements Appetizer {
    public void eat() {
        System.out.println("Eating Rocky Mountain Oysters");
    }
}

public class Gelato implements Dessert {
    public void enjoy() { System.out.println("Enjoying ice cream"); }
}

19.5. Adapter

  • Convert the interface of a class into another interface clients expect
  • Allow classes to work together despite their incompatible interfaces
  • Typically used in development to glue components together, especially if 3rd party libraries are used
  • Also known as wrapper

/**
 * Adapts Enumeration interface to Iterator interface.
 * Does not support remove() operation.
 */
public class EnumerationAsIterator implements Iterator {
  private final Enumeration enumeration;

  public EnumerationAsIterator(Enumeration enumeration) {
    this.enumeration = enumeration;
  }

  public boolean hasNext() {
    return this.enumeration.hasMoreElements();
  }

  public Object next() {
    return this.enumeration.nextElement();
  }

  /**
   * Not supported.
   * @throws UnsupportedOperationException if invoked
   */
  public void remove() {
    throw new UnsupportedOperationException("Cannot remove");
  }
}

19.6. Composite

  • Compose objects into three structures to represent part-whole hierarchies
  • Treat individual objects and compositions of objects uniformly

Figure 12. Composite Design Pattern

images/CompositeDesignPattern.png

public interface Executable { // Component
  public void execute();
}

public class XExecutable implements Executable { // Leaf X
  public void execute() { /* do X */ }
}

public class YExecutable implements Executable { // Leaf Y
  public void execute() { /* do Y */ }
}

public class ExecutableCollection implements Executable { // Composite
  protected Collection executables = new LinkedList();

  public void addExecutable(Executable executable) {
    this.executables.add(executable);
  }

  public void removeExecutable(Executable executable) {
    this.executables.remove(executable);
  }

  public void execute() {
    for (Iterator i = this.executables.iterator(); i.hasNext();) {
      ((Executable) i.next()).execute();
    }
  }
}

19.7. Decorator

  • Attach additional responsibilities to an object dynamically and transparently (run-time)
  • An alternative to sub classing (compile-time)
  • Remove responsibilities no longer needed

Figure 13. Decorator Design Pattern

images/DecoratorPattern.png

A typical example of the decorator pattern can be found in java.io: FilterOutputStream, FilterInputStream, FilterReader, and FilterWriter.

For example, observe how we can decorate an OutputStream:

OutputStream out = new FileOutputStream("somefile");
out = new BufferedOutputStream(out); // buffer before writing
out = CipherOutputStream(out,        // encrypt all data
         Cipher.getInstance("DES/CBC/PKCS5Padding"));
out = new ZipOutputStream(out);      // compress all data
To write our own decorator, we could do:
public ByteCounterOutputStream extends FilterOutputStream {
  private int counter = 0;
  public ByteCounterOutputStream(OutputStream out) {super(out);}
  public void write(int b) throws IOException {
    super.write(b);
    this.counter++;
  }
  public int getCounter() {return this.counter;}
}

We could then wrap out with a byte counter decorator:

ByteCounterOutputStream bout = new ByteCounterOutputStream(out);
for (int b = in.read(); b != -1; b = in.read()) {
  bout.write(b);
}
System.out.println("Written "+ bout.getCounter() + " bytes");

19.8. Chain of Responsibility

  • Decouple senders of requests from receivers
  • Allow more than one object to attempt to handle a request
  • Pass the request along a chain of receivers until it is handled
  • Only one object handles the request

    • Servlet Filter and FilterChain bend this rule
  • Some requests might not be handled

public abstract class MessageHandler {
  private MessageHandler next;
  public MessageHandler(MessageHandler next) {
    this.next = next;
  }
  public void handle(Message message) {
    if (this.next != null) {this.next.handle(message); }
  }
}

public class SpamHandler extends MessageHandler {
  public SpamHandler(MessageHandler next) {
    super(next);
  }
  public void handle(Message message) {
    if (isSpam(message)) {
      // handle spam
    } else {
      super.handle(message);
    }
  }
  ...
}

If a client was given an instance of a handler (created at run time):

MessageHandler handler = new BlackListHandler(new SpamHandler(new ForwardingHandler(new DeliveryHandler(null))));

The client would simply do:

handler.handle(emailMessage);

19.9. Observer / Publish-Subscribe

  • Define a one-to-many dependency between objects
  • When the observed object (subject) changes state, all dependents get notified and updated automatically
  • java.util.Observable, java.util.Observer

Figure 14. Observer Design Pattern

images/ObserverPattern.png

public class Employee extends Observable {
  ...
  public void setSalary(double amount) {
    this.salary = amount;
    super.setChanged();
    super.notifyObservers();
  }
}
public class Manager extends Employee implements Observer {
  public void update(Observable o) {
    if (o instanceof Employee) {
      // note employee's salary, vacation days, etc.
    }
  }
}
public class Department implements Observer {
  public void public void update(Observable o) {
   if (o instanceof Employee) {
     // deduct employees salary from department budget
   }
  }
}
public class Accounting implements Observer {
  public void public void update(Observable o) {
   if (o instanceof Employee) {
     // verify that monthly expenses are in line with the forcast
   }
  }
}

19.10. Strategy

  • Defined family of interchangeable and encapsulated algorithms
  • Change algorithms independently of clients that use them
  • E.g: java.util.Comparator, java.io.FileFilter

Figure 15. Strategy Design Pattern

images/StrategyPattern.png

public interface Comparator {
  public int compare(Object o1, Object o2);
}

public class DateComparator implements Comparator {
  public int compare(Object o1, Object o2) {
    return ((Date) o1).compareTo((Date) o2);
  }
}

public class StringIntegerComparator implements Comparator {
  public int compare(Object o1, Object o2) {
    return Integer.parseInt((String) o1) -
           Integer.parseInt((String) o2);
  }
}

public class ReverseComparator implements Comparator {
 private final Comparator c;
 public ReverseComparator(Comparator c) {this.c = c; }
 public int compare(Object o1, Object o2) {
   return c.compare(o2, o1);
 }
}

To sort integers represented as strings in descending order, you could do:

Arrays.sort(stringArray, new ReverseComparator(new StringIntegerComparator()));

The sort algorithm is independent of the comparison strategy.

19.11. Template

  • Define a skeleton of an algorithm
  • Defer some steps to subclasses
  • Redefine other steps in subclasses
  • Often a result of refactoring common code

Figure 16. Template Design Pattern

images/TemplatePattern.png

public interface Calculator {
  public void calculate(double operand);
  public double getResult();
}

public abstract CalculatorTemplate implements Calculator() {
  private double result;
  private boolean initialized = false;
  public final void calculate(double operand) {
    if (this.initialized) {
      this.result = this.doCalculate(this.result, operand);
    } else {
      this.result = operand;
      this.initialized = true;
    }
  }
  public final double getResult() {
    return this.result; // throw exception if !initialized
  }
  protected abstract doCalculate(double o1, double o2);
}

public class AdditionCalculator extends CalculatorTemplate {
  protected doCalculate(double o1, double o1) {return o1 + o2; }
}

public class SubtractionCalculator extends CalculatorTemplate {
  protected doCalculate(double o1, double o1) {return o1 - o2; }
}

19.12. Data Access Object

  • Abstracts and encapsulates all access to a data source
  • Manages the connection to the data source to obtain and store data
  • Makes the code independent of the data sources and data vendors (e.g. plain-text, xml, LDAP, MySQL, Oracle, DB2)

Figure 17. DAO Design Pattern

images/DAOPattern.png

public class Customer {
  private final String id;
  private String contactName;
  private String phone;
  public void setId(String id) { this.id = id; }
  public String getId() { return this.id; }
  public void setContactName(String cn) { this.contactName = cn;}
  public String getContactName() { return this.contactName; }
  public void setPhone(String phone) { this.phone = phone; }
  public String getPhone() { return this.phone; }
}

public interface CustomerDAO {
  public void addCustomer(Customer c) throws DataAccessException;
  public Customer getCustomer(String id) throws DataAccessException;
  public List getCustomers() throws DataAccessException;
  public void removeCustomer(String id) throws DataAccessException;
  public void modifyCustomer(Customer c) throws DataAccessException;
}

public class MySqlCustomerDAO implements CustomerDAO {
  public void addCustomer(Customer c) throws DataAccessException {
    Connection con = getConnection();
    PreparedStatement s = con.createPreparedStatement("INSERT ...");
    ...
    s.executeUpdate();
    ...
  }
  ...
}