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:
The term "design patterns", in addition to many of the common design patterns comes from the very influential 1994 book Design Patterns: Elements of Reusable Object-Oriented Software. The book names and details 23 patterns.
Today we will look at a handful of patterns that deal with creating objects.
Perhaps the simplest design pattern is the singleton. A singleton is a class from which only one object can ever be made.
These can be used in the following cases:
The code below implements the singleton pattern:
public class ItemGenerator {
// the instance is a private variable inside the class
private static ItemGenerator theInstance;
// return the instance
public static synchronized ItemGenerator instance() {
if (theInstance == null) {
theInstance = new ItemGenerator();
}
return theInstance;
}
// private constructor
private ItemGenerator() {
}
// other methods and data related to the classes purpose
public Item generate() {
// whatever code would go here
}
}
The way this works is by storing the one and only instance as a static variable of the class. Then we have a static method to return that one instance to whoever needs it. Additionally we make the constructor static so that it can only be called from within this class.
To use the object, we must go through the instance
method:
// get a reference to the object
ItemGenerator gen = ItemGenerator.instance();
Item thing = gen.generate();
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 Spell");
}
}
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? For example, we may want to write a method to give the player a level of the game, but do it in a generic way so that we the code could be used for different types of levels: easy ones, hard ones, ones with different themes, etc.
The solution is to use the abstract factory pattern. We start by making an interface with a method to create some type of Enemy:
interface AbstractEnemyFactory {
public Enemy createEnemy();
}
Then we create factory classes for each specific type of thing that can be created:
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 as a parameter to spawn enemies without needing to know what type of enemies are being produced:
public class Level {
public void playerFight(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[]) {
Level l = new Level();
l.playerFight(new WarriorFactory());
}
}
The builder pattern is a pattern used when an object has lots of properties that can be set, some of which are optional. Imagine again we are making a game of sorts with an Enemy class. Imagine we have an item class with many fields to set:
public class Item {
// lots of instance variables to set...
private ItemType type;
private String name;
private int value;
private int weight;
private int strength;
private char symbol;
Imagine that we want to make it so the type and name have to be set for each Item, but that the others can all optionally be set, or left at default values. We could accomplish this with a bunch of constructors:
public Item(ItemType type, String name) {
this.type = type;
this.name = name;
this.value = 10;
this.weight = 5;
this.strength = 1;
this.symbol = 'i';
}
public Item(ItemType type, String name, int value) {
this.type = type;
this.name = name;
this.value = value;
this.weight = 5;
this.strength = 1;
this.symbol = 'i';
}
public Item(ItemType type, String name, int weight) {
this.type = type;
this.name = name;
this.value = 10;
this.weight = weight;
this.strength = 1;
this.symbol = 'i';
}
// ... and so on ...
However, the problem is we would need lots of constructors to cover every combination of fields. In this example, we have 4 fields that can optionally be set, so we would need 16 different constructors! If we add one more field, the number would jump to 32.
This is called the telescoping constructor problem. It's partly caused by Java not allowing default parameter values). Even if we write them all, it would be tough to know which constructors are there and what order the parameters are supposed to be passed in.
One alternative is to have set methods instead. We could leave all the optional things as default values in the constructor, and then have the user of the class call .set methods for each thing they want to change.
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:
public class Item {
// lots of instance variables to set...
private ItemType type;
private String name;
private int value;
private int weight;
private int strength;
private char symbol;
// the builder pattern uses a nested class called builder
public static class Builder {
// these two are required to be specified
private ItemType type;
private String name;
// these are given default values
private int value = 10;
private int weight = 10;
private int strength = 1;
private char symbol = 'i';
// the required ones go into the Builder constructor
public Builder(ItemType type, String name) {
this.type = type;
this.name = name;
}
// methods for setting each optional property
public Builder value(int v) {
value = v;
return this;
}
public Builder weight(int w) {
weight = w;
return this;
}
public Builder strength(int s) {
strength = s;
return this;
}
public Builder symbol(char s) {
symbol = s;
return this;
}
// this method returns the completed Item object
public Item build() {
return new Item(this);
}
} // end of the nested Builder class
// the only constructor takes the builder object as parameter
private Item(Builder builder) {
// copy the things out of the Builder into the Item
this.type = builder.type;
this.name = builder.name;
this.value = builder.value;
this.weight = builder.weight;
this.strength = builder.strength;
this.symbol = builder.symbol;
}
// regular methods for the class would go here...
}
Then, to make an Item object, we make a Builder object first, then call the optional methods to set other fields (in any order), followed by a call to .build:
// build some items this way
Item sword = new Item.Builder(ItemType.Weapon, "Steel Sword")
.symbol('s')
.strength(15)
.build();
Item armor = new Item.Builder(ItemType.Armor, "Iron cuirass")
.value(12)
.symbol('a')
.strength(7)
.weight(13)
.build();
Note that we must use the Builder as the only Item constructor is private. Also, the order of the Builder calls is not important, unlike constructor parameters, and the code is easier to read (though funky if you've not seen the pattern before!)
Copyright © 2024 Ian Finlayson | Licensed under a Creative Commons BY-NC-SA 4.0 License.