Home CPSC 330

Design Patterns 1

Overview

In software development, often times the same problem re-appears multiple times in a project, or across several projects.

A design pattern is a general approach to solving a problem that can be applied in different scenarios.

Learning design patterns is important for two reasons:

  1. They allow you to deal with common issues relatively easily.
  2. You will be able to more easily understand code that uses them.

Today we will discuss creational design patterns dealing with making new objects.


The Singleton Pattern

A singleton is an object which is the only instance of its class.

These can be used in the following cases:

The code below implements the singleton pattern:


// SingletonExample.java

// the Singleton class
class Singleton {
  // the instace is a private variable inside the class
  private static final Singleton instance = new Singleton();

  // return the instance
  public static Singleton getInstance() {
    return instance;
  }

  // private constructor (so it can't be called outside!)
  private Singleton() { }

  // other methods and data related to the classes purpose
  public void print() {
    System.out.println("Hello!");
  }
}

public class SingletonExample {
  public static void main(String args[]) {
    // get a reference to the object
    Singleton thing = Singleton.getInstance();
    thing.print();
  
    // can't create another one!
    // Singleton thing2 = new Singleton();
    // thing2.print();
  }
}

The singleton pattern essentially provides a global object that can be accessed any where. Using them too much can lead to code that is brittle and hard to maintain.


Abstract Factories

The abstract factory pattern is used to provide a single interface for creating different types of objects. For example, if we have a game with multiple types of enemies, we can represent them with an interface and multiple classes implementing it:


interface Enemy {
  public void attack();
  public void defend();
}

class Warrior implements Enemy {
  public void attack() {
    System.out.println("Sword Attack");
  }

  public void defend() {
    System.out.println("Uses Shield");
  }
}

class Mage implements Enemy {
  public void attack() {
    System.out.println("Spell Attack");
  }

  public void defend() {
    System.out.println("Uses a Ward");
  }
}

Now we can write code that uses an enemy without needing to know what type of enemy we have.

But what if we want to write code that creates enemies without knowing what type we are creating?

The solution is to create an abstract factory:


interface AbstractEnemyFactory {
  public Enemy createEnemy();
}
Then we create factory classes for each type:

class WarriorFactory implements AbstractEnemyFactory {
  public Enemy createEnemy() {
    return new Warrior();
  }
}

class MageFactory implements AbstractEnemyFactory {
  public Enemy createEnemy() {
    return new Mage();
  }
}

Now we can write code that takes an AbstractEnemyFactory object to spawn enemies without needing to know what type of enemies are being produced:


class Dungeon {
  public void crawl(AbstractEnemyFactory factory) {
    // get an enemy and have them attack
    Enemy e = factory.createEnemy();
    e.attack();
  }
}

And we can then call this method and pass in whichever type of factory we want:


public class AbstractFactoryExample {
  public static void main(String args[]) {
    Dungeon d = new Dungeon();
    d.crawl(new WarriorFactory());
  }
}

The complete code listing for this example can be seen here.


Builders

The builder pattern is a pattern in which a class is responsible for building objects of another class. This is used when constructing an object is non-trivial and has multiple options:


class Enemy {
  public Enemy(int hp);
  public Enemy(int hp, Weapon weapon);
  public Enemy(int hp, Armor armor);
  public Enemy(int hp, Weapon weapon, Armor armor);
  public Enemy(int hp, int gold);
  public Enemy(int hp, Weapon weapon, int gold);
  public Enemy(int hp, Armor armor, int gold);
  public Enemy(int hp, Weapon weapon, Armor armor, int gold);
}

This is called the telescoping constructor problem. (Java does not allow default parameter values). This can be a problem since it would be tough to know which constructors are there and what order the parameters are passed in. Additionally, it could be possible that dozens of parameters could be needed which is awkward.

One alternative is to have set methods instead:


class Enemy {
  public Enemy(int hp);

  public void setWeapon(Weapon weapon);
  public setArmor(Armor armor);
  public setGold(int gold);
}

However we may not want to rely on the user calling each method that is needed. Also for some objects they may be in an inconsistent state between the constructor and set calls.

Below is an example of how this problem can be solved using the Builder pattern:


class Enemy {
  private int hp;
  private Weapon weapon;
  private Armor armor;
  private int gold;

  // the Builder class is nested inside of them
  public static class Builder {
    // required
    private int hp;

    // optional with default values
    private Weapon weapon = null;
    private Armor armor = null;
    private int gold = 5;

    // required ones are in the constructor
    public Builder(int hp) {
      this.hp = hp;
    }

    // methods for adding each property
    public Builder weapon(Weapon w) {
      weapon = w;
      return this;
    }
    public Builder armor(Armor a) {
      armor = a;
      return this;
    }
    public Builder gold(int g) {
      gold = g;
      return this;
    }

    // the build method returns a Enemy object
    public Enemy build() {
      return new Enemy(this);
    }
  }

  // private constructor takes a builder
  private Enemy(Builder b) {
    hp = b.hp;
    weapon = b.weapon;
    armor = b.armor;
    gold = b.gold;
  }
}

We can now use the Builder to create Enemy objects:


Enemy a = new Enemy.Builder(150).armor(new Armor()).gold(25).build();
Enemy b = new Enemy.Builder(80).weapon(new Weapon()).build();

Note that we must use the Builder as the only Enemy constructor is private. Also, the order of the Builder calls is not important, unlike constructor parameters, and the code is easy to read.


Prototypes

The idea behind the prototype pattern is that instead of making a new object from scratch each time we need one, we will create an object, called the protoype, from which other objects are copied.

In Java, this can be done by implementing the Cloneable interface. To implement this, we can call super.clone which copies each variable one by one.

The example below demonstrates how this can be done:


class Enemy implements Cloneable {
  private int hp;
  private Weapon weapon;
  private Armor armor;
  private int gold;

  public Enemy(int h, Weapon w, Armor a, int g) {
    hp = h;
    weapon = w;
    armor = a;
    gold = g;
  }

  // set methods
  public void setHp(int h) {
    hp = h;
  }
  public void setArmor(Armor a) {
    armor = a;
  }
  public void setWeapon(Weapon w) {
    weapon = w;
  }
  public void setGold(int g) {
    gold = g;
  }

  // override the clone method
  public Enemy clone() {
    try {
      return (Enemy)super.clone();
    } catch(Exception e) {
      return null;
    }
  }
}

Then we can make objects as follows:


Enemy prototype = new Enemy(150, new Weapon(), new Armor(), 5);

// clone the prototype and customize
Enemy rich = prototype.clone();
rich.setGold(100);

// clone the prototype and customize
Enemy defenseless = prototype.clone();
defenseless.setWeapon(null);
defenseless.setArmor(null);

This pattern is best when objects are large and expensive to construct.

In languages like Javascript and Lua do not support classes and the only way to create new objects is by copying a prototype.

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