In this chapter, we'll explore the benefits and applications of using creational design patterns.
Why Use Creational Patterns?
For your systems to work, you need to instantiate objects. Typically, you make the one you need when you need it. You'll see code like this a lot:
new someClassName();
But there is a problem with that. You have no flexibility whatsoever. You are making an object of type someClassName. Forever. Unless you go back in and change it. And as mentioned, going back in and changing existing code can lead to problems. So what can you do instead? Well, try to avoid using the word “new” as much as possible.
But new is the way to make objects. How can that be avoided? 🤔
You can’t avoid it entirely, but you can use it judiciously. There is an adage in software development that says you can solve any problem by adding yet one more level of indirection. If you apply that, you can defer the creation of the object to something else. That other thing is still called new, but you gain flexibility in your code through one extra level of indirection.
And this is where creational design patterns can help! Let’s look at some specific examples.
What is the Factory Pattern?
The factory pattern is the simplest of the “factory” approaches, as defined in the Gang of Four book. But they all follow roughly the same idea. Let other objects make objects for you. The factory object is configured in some fashion and then asked to create the wanted object.
In Wisconsin, where I grew up, they play a game that only uses a 32 card deck. How easy would it be for us to modify our card game to use this new kind of deck? It’s generated inside the controller or passed in. So we move the deck generation into a factory class. A factory is a class that makes objects of a different class. If you change the factory, you change what gets created. So we can have 32 and 52 card factories. We pick the one for the type of game we are playing. It creates the deck, and we pass the deck into the controller.
Here is a DeckFactory class that makes normal, small, and test decks, depending on the parameter:
public class DeckFactory {
public static Deck makeDeck(DeckType type) {
switch (type) {
case Normal: return new NormalDeck();
case Small: return new SmallDeck();
case Test: return new TestDeck();
}
// fallback
return new NormalDeck();
}
}
Then any class that needed a normal deck could call:
Deck myDeck = DeckFactory.makeDeck(DeckType.Normal);
Let’s try it together!
We added flexibility by letting another object (DeckFactory) make the object we want in our game (NormalDeck, SmallDeck, or TestDeck). Patterns can be modified to suit our needs. We could add a method to DeckFactory so that we preconfigure the kind of decks it makes. Then the makeDeck()
method wouldn’t need a parameter and return the type of deck previously configured. This is handy when you write unit tests. In testing, the deck type would be set to Test
. But during a game, we would set it to Normal
or Small
.
The factory method works when we want the flexibility to pick between specific implementations at run time. Just tell the factory what you want, and it makes that specific kind.
What is the Prototype Pattern?
The prototype creational patterns make a new object by cloning an already existing one. Again, you preconfigure an object (or objects) the way you want. Then pick the one you want your new object to be like, and ask the prototype to return a clone of itself.
It's like how cells in your body create new ones. When a cell splits, it creates an exact duplicate.
OK, but how does this work in Java?
For example, in Java, an assignment is by reference. That is, in the code below, a and b both reference the same object.
SomeClass a = new SomeClass();
SomeClass b = a;
// this also changes a.someField
// since a and b refer to the same thing
b.someField = 5;
If you wanted a completely new object for b, but have it contain the same values as a did at the moment of copying, you would use clone:
SomeClass a = new SomeClass();
a.someField = 1202;
SomeClass b = a.clone();
// a.someField remains 1202
// since it is a different object
b.someField = 5;
And ta-da! ✨
Cloning is a handy pattern when you want an exact duplicate of an existing object, or at least very close. The alternative is to create an object in the normal way (call new), and then set all the values to match the item you want to copy. But that's a lot of code (and typing energy).
One usage is a catalog lookup. Generate a set of preconfigured objects, and store them in a catalog with a corresponding lookup key. Then, when you need an object of a particular type, you ask the catalog to provide one. The catalog finds the object corresponding to the key, clones it, and gives you a new object to use.
What is the Builder Pattern?
You often build a single object out of constituent pieces. But how do you control what pieces go into the whole? The builder pattern. The builder follows an algorithm to determine what pieces to build. But the pieces can vary.
If you have ever ordered a meal at a fast-food restaurant, you have experienced the builder pattern. Every meal consists of the main item, a side item, and a drink. But the items can change. For the main item, you may choose from a taco, hamburger, or pancakes. The side item might be beans, fries, or toast. The drink can be a soft drink, coffee, or tea. The person assembling your order grabs one of each specific item, but the result always consists of three items. 🍔🍟🥛
In our card game, we could use this pattern (although it's a little heavy- handed). We have a couple of selection options. We can pick the size of the deck, and we can pick the high/low card winner options. Our builder object would return the appropriate implementations, based on those selections.
public interface GameBuilder {
Game getGame();
}
public class NormalHighCardGameBuilder implements GameBuilder {
public Game getGame() {
return new Game(DeckFactory.makeDeck(DeckType.Normal),
EvaluatorFactory.makeEvaluator(EvaluatorType.High));
}
}
public class SmallHighCardGameBuilder implements GameBuilder {
public Game getGame() {
return new Game(DeckFactory.makeDeck(DeckType.Small),
EvaluatorFactory.makeEvaluator(EvaluatorType.High));
}
}
What are Singletons?
As discussed in the bad ideas chapter, singletons open up the opportunity to grow depending on the global state. Review that portion if you need a refresher. One of the original authors of the design patterns book said if they could rewrite the book, they'd leave that one out.
If we added a SoundManager and opted to make it a singleton, it would look like the following:
class SoundManager
{
// need to remember the one instance somewhere
// so we'll keep a static variable
private static SoundManager _instance = null;
// like any other class, attributes exist
private int currentSoundLevel;
// make the constructor private
// so it can't be called by anything other than
// methods defined in this class
private SoundManager() {
currentSoundLevel = 11;
}
// the method clients call to gain access to the singleton
public static SoundManager getInstance() {
// if we haven't made one before, make one now
if (_instance == null)
_instance = new SoundManager();
// return the one we made
// either this time, or any time before
return _instance;
}
// nothing special about any other method
public void setVolume(int value) {
currentSoundLevel = value;
}
}
The main part is that the constructor is hidden from callers (made private), but we still need to give them a method to access the one SoundManager we are creating, so we have the getInstance()
method.
Let's Recap!
Creational patterns add a level of indirection to object creation, adding flexibility to applications.
The factory pattern has an object create another object. You can configure the factory to make an object just the way you want it.
The prototype pattern creates a new object by cloning an existing object.
The builder pattern creates a complex object by assembling various other objects into the single, complex item.
Now that you know how to build objects in different ways, let's look at how to organize objects into more complex compositions. The next chapter will look at some of the structural design patterns.